Autodiseño con UIViews ocultos

Siento que es un paradigma bastante común para mostrar / ocultar UIViews , más a menudo UILabels , dependiendo de la lógica de negocios. 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.

Lista de funciones dinámicas

En este momento tengo un espacio superior de 10 píxeles desde el botón hasta la última etiqueta, que obviamente no se deslizará cuando la etiqueta esté oculta. A partir de ahora creé una salida a esta restricción y modificando la constante según la cantidad de tags 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.

Obviamente, podría crear nuevas restricciones dependiendo de mis tags 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, sin embargo, solo modificar la constante desde el contexto de la vista oculta requiere una sola línea de código con cálculo simple. Recreando nuevas restricciones con constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant: parece tan pesado.

Edite Feb 2018 : vea la respuesta de Ben con UIStackView s

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.

Mi preferencia personal para mostrar / ocultar vistas es crear una IBOutlet con la restricción de ancho o altura adecuada.

Luego, 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 la vista A y ve B con un espacio horizontal de x . Cuando la vista A, la constant ancho se establece en 0.f entonces, la vista B se moverá hacia la izquierda para llenar ese espacio.

No es necesario agregar o eliminar restricciones, que es una operación de gran peso. Simplemente actualizar la constant la restricción hará el truco.

La solución de usar una constante 0 cuando está oculta y otra constante si la muestra de nuevo es funcional, pero no es satisfactorio 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 solucion.

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 lo haces:

1. configure un ancho (o alto) de 0 en el constructor de interfaz con baja prioridad.

configuración de la regla de ancho 0

Interface Builder no gritará sobre 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. Agregue una salida para que pueda modificar la prioridad de la restricción de ancho en el código:

conexión de salida

3. Ajuste la prioridad cuando oculta su elemento:

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

En este caso, asigno un mapa de la altura de la etiqueta Author a una IBOutlet adecuada:

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

y cuando configuro la altura de la restricción a 0.0f, 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

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

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

Como conectar restricciones a IBOutlets y establecer sus constantes en 0 me sentí asqueroso (y NSLayoutConstraint advertencias de NSLayoutConstraint cuando tu vista tenía subvistas), decidí crear una extensión que UIView un enfoque simple y detallado para ocultar / mostrar una UIView que tiene restricciones de diseño automático.

Simplemente oculta la vista y elimina las 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 .

Construyo categoría para actualizar las restricciones fácilmente:

 [myView1 hideByHeight:YES]; 

Responda aquí: Ocultar UILiew autolayout: Cómo obtener NSLayoutConstraint existente para actualizar este

enter image description here

Acabo de descubrir que para hacer que UILabel no ocupe espacio, debes ocultarlo Y configurar 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.

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

Asegúrate de que tus propiedades sean strong

en el código, solo tiene que establecer la constante en 0 y activarla , aunque oculte el contenido, o desactívelo para mostrar el contenido. Esto es mejor que estropear el valor constante de un ahorro, restaurándolo. No olvides llamar a layoutIfNeeded luego.

Si el contenido que se oculta está agrupado, la mejor práctica es poner todo en una vista y agregar las restricciones a esa 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 su configuración, puede probarla en IntefaceBuilder estableciendo su restricción en 0 y luego volver 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.

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.

Luego, cree otra restricción que especifique la altura entre el botón y la etiqueta superior cuando el segundo botón esté oculto. Cree una salida a la segunda restricción y asegúrese de que esta toma tenga un puntero fuerte. A continuación, desmarque la checkbox 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.

Finalmente, cuando oculta la segunda etiqueta, alternar la propiedad .isActive de estas restricciones y llamar a setNeedsDisplay()

Y eso es todo, no hay números mágicos, no hay matemática, si tiene múltiples restricciones para encender y apagar, incluso puede usar colecciones de salida 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 es más simple que la creación dinámica de vistas y restricciones programáticamente (que es lo que creo que quería evitar si Estoy leyendo la pregunta correctamente).

He creado un ejemplo simple, 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

Aquí hay muchas soluciones, 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).

Conecte la restricción B a una IBOutlet. Luego, cuando oculta el elemento, solo necesita establecer la prioridad de restricción en alta (750):

constraintB.priority = .defaultHigh

Pero cuando el elemento está visible, vuelva a establecer la prioridad en baja (250):

constraintB.priority = .defaultLow

La ventaja (ciertamente menor) de este enfoque sobre el cambio es isActive para la restricción B es que todavía tiene una restricción de trabajo si el elemento transitorio se elimina de la vista por otros medios.

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 instancias de UIImageView), selecciona cuáles se deben volver a agregar 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 un diseño y espaciado limpio y consistente. Mi código usa Masonry, lo recomiendo encarecidamente: https://github.com/SnapKit/Masonry