AutoLayout con UIViews ocultos?


145

Siento que es un paradigma bastante común mostrar/ocultar UIViews, más a menudo UILabels, dependiendo de la lógica comercial. Mi pregunta es, ¿cuál es la mejor manera de usar AutoLayout para responder a las vistas ocultas como si su marco fuera 0x0. Aquí hay un ejemplo de una lista dinámica de 1-3 características.

Dynamic features list

Ahora mismo tienen un espacio superior 10px desde el botón a la última etiqueta, que obviamente no se deslizará hacia arriba cuando el la etiqueta está oculto. A partir de ahora creé una salida a esta restricción y modificando la constante según la cantidad de etiquetas que estoy mostrando. Esto es obviamente un poco raro ya que estoy usando valores constantes negativos para presionar el botón sobre los marcos ocultos. También es malo porque no está restringido a elementos de diseño reales, solo cálculos furtivos estáticos basados ​​en alturas conocidas/relleno de otros elementos, y obviamente luchando contra lo que AutoLayout fue creado.

Podría, obviamente, crear nuevas restricciones dependiendo de mis etiquetas dinámicas, pero eso implica mucha microgestión y mucha verbosidad para tratar de colapsar algunos espacios en blanco. ¿Hay mejores enfoques? ¿Cambiar el tamaño de foto 0,0 y dejar que AutoLayout haga su trabajo sin manipulación de restricciones? ¿Eliminar las vistas por completo?

Honestamente, simplemente modificar la constante desde el contexto de la vista oculta requiere una sola línea de código con cálculo simple. Volver a crear nuevas restricciones con constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant: parece muy pesado.

Editar Feb 2018: Véase la respuesta de Ben con UIStackView s

  0

Gracias Ryan por esta pregunta. Me estaba volviendo loco qué hacer para los casos como lo has pedido. Cada vez que reviso el tutorial para el diseño automático, la mayoría de ellos dice que se refieren al sitio tutorial de Raywenderlich, que me resulta un poco difícil de entender. 07 feb. 142014-02-07 11:56:00

225

Mi preferencia personal para mostrar/ocultar puntos de vista es crear un IBOutlet con el ancho apropiado o restricción de altura.

A continuación, actualizo el valor constant a 0 para ocultarlo, o cualquiera que sea el valor que debe mostrarse.

La gran ventaja de esta técnica es que se mantendrán las restricciones relativas. Por ejemplo, digamos que tiene una vista A y ve B con una brecha horizontal de x. Cuando la vista A ancho constant se establece en 0.f, entonces la vista B se moverá hacia la izquierda para llenar ese espacio.

No es necesario agregar o quitar restricciones, que es una operación pesada. Simplemente actualizar la restricción constant hará el truco.

  0

Me gusta mucho esta respuesta. Entonces, básicamente, si estoy entendiendo correctamente, establezco restricciones fijas de altura o peso, creo una IBOutlet para esas restricciones, y si esa vista está oculta, simplemente cambie a una constante de 0.f y el resto de los elementos deberían estar en su lugar. 25 oct. 132013-10-25 11:23:59

+1

exactamente. Siempre que, en este ejemplo, coloque la vista B a la derecha de la vista A utilizando un espacio horizontal. Utilizo esta técnica todo el tiempo y funciona muy bien 25 oct. 132013-10-25 11:47:30

  0

@MaxMacLeod ¿Qué pasa si la etiqueta es potencialmente multi-línea? En este caso, una restricción de altura no sería apropiada, ¿no? 29 ene. 142014-01-29 23:10:43

  0

@markdorison Hola Mark Vi tu publicación en twitter en realidad. Lo tomo en su caso, ¿la etiqueta es de altura variable? Esto debería funcionar bien, pero la altura, por supuesto, tendrá que ajustarse al contenido utilizando boundingRectWithSize. Me pregunto si necesitarás precalcular las alturas para cada celda. 30 ene. 142014-01-30 08:59:30

+7

@MaxMacLeod Solo para asegurarse: si el espacio entre las dos vistas es x, para que la vista B comience desde la posición A de la vista, también debemos cambiar la constante de restricción de espacio a 0, ¿verdad? 07 mar. 142014-03-07 09:14:09

+1

@kernix sí, si es necesario que haya una restricción de espacio horizontal entre las dos vistas. constante 0, por supuesto, no significa brecha. Así que ocultar la vista A estableciendo su ancho en 0 movería la vista B hacia la izquierda para que quede alineada con el contenedor. ¡Por cierto, gracias por los votos al alza! 07 mar. 142014-03-07 09:34:14

+2

@MaxMacLeod Gracias por su respuesta. Intenté hacer esto con una UIView que contiene otras vistas, pero las subvistas de esa vista no están ocultas cuando establezco su restricción de altura en 0. ¿Se supone que esta solución funciona con vistas que contienen subvistas? ¡Gracias! 27 ago. 142014-08-27 23:49:46

+5

También agradecería una solución que funcione para una UIView que contenga otras vistas ... 23 oct. 142014-10-23 13:11:23

  0

Creo que su solución funcionaría para mi problema. Si es así, podría proporcionar un ejemplo de código ya que soy nuevo en el desarrollo de IOS y las limitaciones realmente me confunden. Esta es la publicación http://stackoverflow.com/questions/27347937/changing-position-of-a-uilabel-for-a-custo @MaxMacLeod 09 dic. 142014-12-09 17:49:04

  0

Así que pregunta sobre esto, ya que esto es realmente diseño automático, ¿cómo lo harías? sabe a qué ajustar la altura? ¿Le gustaría que el control restablezca su altura y, de ser así, cómo? 06 sep. 152015-09-06 23:50:11

  0

@RobBonner gran pregunta. Una etiqueta puede tener dimensiones establecidas automáticamente con un tamaño de contenido intrínseco. Sin embargo, eso no va a funcionar en este caso, ya que tendrá una restricción de altura explícita. Por lo tanto, tendrá la altura codificada de forma fija en el Xib, que se indicará en el controlador de vista, o se configurará por completo en el controlador de vista. 07 sep. 152015-09-07 05:54:44

  0

Gracias @Max MacLeod Pero tengo un pequeño problema. Mi vista tiene márgenes verticales, por lo que incluso si agrego una restricción de altura cero con prioridad alta o requerida, entonces veo un error en la consola porque estas restricciones no pueden satisfacerse al mismo tiempo. 10 dic. 152015-12-10 12:49:41


3

construyo categoría para actualizar fácilmente limitaciones:

[myView1 hideByHeight:YES]; 

respuesta aquí: Hide autolayout UIView : How to get existing NSLayoutConstraint to update this one

enter image description here

+7

Lamentablemente se obtiene un doble espacio allí, justo donde apunta la flecha amarilla. 02 nov. 152015-11-02 19:28:22

  0

Como con las otras soluciones anteriores, creo. No es ideal en absoluto. 10 nov. 152015-11-10 16:58:27


4

Me sorprende que no hay un enfoque más elegante proporcionada por UIKit para este comportamiento deseado. Parece una cosa muy común querer hacer.

Desde la conexión de las restricciones a IBOutlets y establecer sus constantes a 0 sintieron asco (y causó NSLayoutConstraint advertencias cuando su vista tenía subvistas), he decidido crear una extensión que da una simple, stateful approach to hiding/showing a UIView that has Auto Layout constraints

Simplemente se esconde de la vista y elimina restricciones exteriores. Cuando vuelve a mostrar la vista, agrega las restricciones. La única advertencia es que deberá especificar restricciones flexibles de conmutación por error para las vistas circundantes.

Editar Esta respuesta está dirigida a iOS 8.4 y versiones posteriores. En iOS 9, solo usa el enfoque UIStackView.

+1

Este es mi enfoque preferido también. Es una mejora significativa en otras respuestas aquí, ya que no solo reduce la vista a tamaño cero. El enfoque squash tendrá problemas para nada más que pagos bastante triviales. Un ejemplo de esto es la gran brecha donde se encuentra el elemento oculto en [esta otra respuesta] (http://stackoverflow.com/a/22386997/2547229). Y puede terminar con violaciones de restricciones como se menciona. 10 nov. 152015-11-10 17:04:20

  0

@DanielSchlaug Gracias por señalarlo. He actualizado la licencia para MIT. Sin embargo, sí necesito un alto de mental cinco cada vez que alguien implementa esta solución. 15 ene. 162016-01-15 17:12:35


90

La solución de usar una constante 0 cuando está oculta y otra constante si la muestra de nuevo es funcional, pero es insatisfactorio si su contenido tiene un tamaño flexible. Tendría que medir su contenido flexible y establecer una constante de vuelta. Esto se siente mal y tiene problemas si el contenido cambia de tamaño debido a los eventos del servidor o de la interfaz de usuario.

Tengo una mejor solución.

La idea es establecer que la regla de 0 altura tenga alta prioridad cuando ocultemos el elemento para que no ocupe espacio de autodiseño.

Así es como se hace eso:

1. estableció una anchura (o altura) de 0 en el constructor de interfaces con una baja prioridad.

setting width 0 rule

Interface Builder no gritar acerca de los conflictos porque la prioridad es baja. Pruebe el comportamiento de altura estableciendo la prioridad en 999 temporalmente (1000 está prohibido mutar programáticamente, por lo que no lo usaremos). El creador de interfaces probablemente ahora gritará sobre restricciones conflictivas. Puede solucionar esto estableciendo prioridades en objetos relacionados a 900 o más.

2. Añadir una salida para que pueda modificar la prioridad de la restricción de ancho de código:

outlet connection

3. Ajustar la prioridad cuando oculta su elemento:

cell.alertTimingView.hidden = place.closingSoon != true 
cell.alertTimingWidth.priority = place.closingSoon == true ? 250 : 999 
  0

@SimplGy ¿puede decirme por favor qué es este lugar.closingSoon? 17 feb. 172017-02-17 06:09:14

  0

No es parte de la solución general, @Niharika, es exactamente lo que era la regla de negocio en mi caso (mostrar la vista si el restaurante está cerrando pronto). 19 feb. 172017-02-19 00:35:58


67

UIStackView es probablemente el camino a seguir para iOS 9+. No solo maneja la vista oculta, sino que también eliminará espacios y márgenes adicionales si está configurada correctamente.

+5

Tenga cuidado al usar UIStackView en una UITableViewCell. Para mí, una vez que la vista se volvió muy complicada, el desplazamiento comenzó a ponerse entrecortado, donde tartamudeaba cada vez que se movía una celda hacia adentro o hacia afuera. Bajo las cubiertas, StackView está agregando/eliminando restricciones y esto no necesariamente será lo suficientemente eficiente para un desplazamiento suave. 16 feb. 162016-02-16 18:31:07

+5

Sería bueno dar un pequeño ejemplo de cómo la vista de pila maneja esto. 09 sep. 162016-09-09 19:47:14

  0

@Kento ¿Sigue siendo un problema en iOS 10? 31 oct. 172017-10-31 20:31:00

+2

Cinco años después ... ¿Debería actualizar y establecer esto como la respuesta aceptada? Ha estado disponible para tres ciclos de lanzamiento ahora. 13 feb. 182018-02-13 06:02:20


5

Subclase la vista y reemplaza func intrinsicContentSize() -> CGSize. Simplemente devuelva CGSizeZero si la vista está oculta.

+2

Esto es genial. Sin embargo, algunos elementos de la interfaz de usuario necesitan un activador para 'invalidateIntrinsicContentSize'. Puedes hacer eso en un 'setHidden' reemplazado. 07 mar. 162016-03-07 09:23:19


0

Proporcionaré mi solución también, para ofrecer variedad.) Creo que crear una salida para el ancho/alto de cada artículo más el espaciado es simplemente ridículo y hace explotar el código, los posibles errores y el número de complicaciones.

Mi método elimina todas las vistas (en mi caso las instancias de UIImageView), selecciona las que deben agregarse nuevamente, y en un ciclo, agrega cada una y crea nuevas restricciones. En realidad es muy simple, por favor sigue.Aquí está mi código rápido y sucio para hacerlo:

// remove all views 
[self.twitterImageView removeFromSuperview]; 
[self.localQuestionImageView removeFromSuperview]; 

// self.recipients always has to be present 
NSMutableArray *items; 
items = [@[self.recipients] mutableCopy]; 

// optionally add the twitter image 
if (self.question.sharedOnTwitter.boolValue) { 
    [items addObject:self.twitterImageView]; 
} 

// optionally add the location image 
if (self.question.isLocal) { 
    [items addObject:self.localQuestionImageView]; 
} 

UIView *previousItem; 
UIView *currentItem; 

previousItem = items[0]; 
[self.contentView addSubview:previousItem]; 

// now loop through, add the items and the constraints 
for (int i = 1; i < items.count; i++) { 
    previousItem = items[i - 1]; 
    currentItem = items[i]; 

    [self.contentView addSubview:currentItem]; 

    [currentItem mas_remakeConstraints:^(MASConstraintMaker *make) { 
     make.centerY.equalTo(previousItem.mas_centerY); 
     make.right.equalTo(previousItem.mas_left).offset(-5); 
    }]; 
} 


// here I just connect the left-most UILabel to the last UIView in the list, whichever that was 
previousItem = items.lastObject; 

[self.userName mas_remakeConstraints:^(MASConstraintMaker *make) { 
    make.right.equalTo(previousItem.mas_left); 
    make.leading.equalTo(self.text.mas_leading); 
    make.centerY.equalTo(self.attachmentIndicator.mas_centerY);; 
}]; 

Obtengo una distribución y espaciado limpios y uniformes. Mi código utiliza mampostería, lo recomiendo encarecidamente: https://github.com/SnapKit/Masonry


11

En este caso, puedo asignar la altura de la etiqueta de autor a un IBOutlet apropiado:

@property (retain, nonatomic) IBOutlet NSLayoutConstraint* authorLabelHeight; 

y cuando me puse la altura de la restricción a 0,0 f, conservamos el "relleno" porque la altura del botón Reproducir lo permite.

cell.authorLabelHeight.constant = 0; //Hide 
cell.authorLabelHeight.constant = 44; //Show 

enter image description here


3

Me acabo de enterar de que para conseguir una UILabel para no ocupar espacio, hay que ocultarlo y establecer su texto en una cadena vacía. (iOS 9)

Conocer este hecho/error podría ayudar a algunas personas a simplificar sus diseños, posiblemente incluso el de la pregunta original, así que pensé en publicarlo.

  0

No creo que deba ocultarlo. Establecer su texto en una cadena vacía debería ser suficiente. 11 jul. 162016-07-11 12:27:41


3

La mejor práctica es que, una vez que todo tenga las restricciones de diseño correctas, agregue una altura o restricción, dependiendo de cómo quiera que se muevan las vistas circundantes y conecte la restricción a una propiedad IBOutlet.

asegurarse de que sus propiedades son strong

en el código yo sólo hay que establecer la constante a 0 y activar que, aunque ocultar el contenido o desactivar que se muestre el contenido. Esto es mejor que estropear el valor constante y guardarlo, restaurándolo. No olvide llamar después al layoutIfNeeded.

Si el contenido que se oculta se agrupa, lo mejor es poner todo en una vista y añadir las restricciones a la vista

@property (strong, nonatomic) IBOutlet UIView *myContainer; 
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *myContainerHeight; //should be strong!! 

-(void) showContainer 
{ 
    self.myContainerHeight.active = NO; 
    self.myContainer.hidden = NO; 
    [self.view layoutIfNeeded]; 
} 
-(void) hideContainer 
{ 
    self.myContainerHeight.active = YES; 
    self.myContainerHeight.constant = 0.0f; 
    self.myContainer.hidden = YES; 
    [self.view layoutIfNeeded]; 
} 

Una vez que tenga la configuración que pueda pruébalo en IntefaceBuilder estableciendo tu restricción en 0 y luego regresa al valor original. No se olvide de verificar otras prioridades de restricciones para que cuando esté oculto no exista ningún conflicto. Otra forma de probarlo es ponerlo en 0 y establecer la prioridad en 0, pero no debe olvidarse de restaurarlo nuevamente a la prioridad más alta.

+1

Esto se debe a que la restricción y la vista no siempre podrían ser retenidas por la jerarquía de vistas, por lo que si desactiva la restricción y oculta la vista aún las conserva para mostrarlas nuevamente independientemente de lo que la jerarquía de la vista decida retener o no . 30 may. 162016-05-30 09:06:48


2

Mi método preferido es muy similar al sugerido por Jorge Arimany.

Prefiero crear múltiples restricciones. Primero crea tus restricciones para cuando la segunda etiqueta esté visible. Cree una salida para la restricción entre el botón y la segunda etiqueta (si está usando objc, asegúrese de que sea fuerte). Esta restricción determina la altura entre el botón y la segunda etiqueta cuando está visible.

A continuación, cree otra restricción que especifique la altura entre el botón y la etiqueta superior cuando se oculta el segundo botón. Cree una salida a la segunda restricción y asegúrese de que esta toma tenga un puntero fuerte. A continuación, desmarque la casilla installed en el generador de interfaz y asegúrese de que la prioridad de la primera restricción sea menor que esta segunda prioridad de restricciones.

Por último, cuando se oculta la segunda etiqueta, alternar la propiedad .isActive de estas restricciones y llamar setNeedsDisplay()

Y eso es todo, no hay números mágicos, sin matemáticas, si tiene varias limitaciones para encender y apagar incluso se puede use colecciones de salidas para mantenerlas organizadas por estado. (Es decir, mantenga todas las restricciones ocultas en una OutletCollection y las no ocultas en otra y simplemente itere sobre cada colección alternar su estado .isActive).

Sé que Ryan Romanchuk dijo que no quería usar múltiples restricciones, pero creo que esto no es microgestión-y, y es más simple que dinámicamente la creación de vistas y restricciones programáticamente (que es lo que creo que le faltaba para evitar si estoy leyendo la pregunta correcta).

He creado un ejemplo sencillo, espero que sea útil ...

import UIKit 

class ViewController: UIViewController { 

    @IBOutlet var ToBeHiddenLabel: UILabel! 


    @IBOutlet var hiddenConstraint: NSLayoutConstraint! 
    @IBOutlet var notHiddenConstraint: NSLayoutConstraint! 


    @IBAction func HideMiddleButton(_ sender: Any) { 

     ToBeHiddenLabel.isHidden = !ToBeHiddenLabel.isHidden 
     notHiddenConstraint.isActive = !notHiddenConstraint.isActive 
     hiddenConstraint.isActive = !hiddenConstraint.isActive 

     self.view.setNeedsDisplay() 
    } 
} 

enter image description here


0

Hay una gran cantidad de soluciones aquí pero mi enfoque habitual es diferente de nuevo :)

Configure dos conjuntos de restricciones similares a las respuestas de Jorge Arimany y TMin:

enter image description here

Las tres restricciones marcadas tienen el mismo valor para la constante. Las restricciones marcadas como A1 y A2 tienen su Prioridad establecida en 500, mientras que la restricción marcada B tiene su Prioridad establecida en 250 (o UILayoutProperty.defaultLow en el código).

Conecta la restricción B a una IBOutlet. Entonces, cuando se oculta el elemento, sólo tiene que establecer la prioridad limitación a alta (750):

constraintB.priority = .defaultHigh

Pero cuando el elemento es visible, establezca la prioridad de nuevo a bajo (250):

constraintB.priority = .defaultLow

el (la verdad es menor de edad) ventaja de este enfoque sobre solo cambiando isActive para la restricción B es que todavía tiene una restricción de trabajo si el elemento transitoria se elimina de la vista por otros medios.