UIStackView "No se pueden satisfacer las restricciones simultáneamente" en las vistas ocultas "aplastadas"

95

Cuando mis "filas" de UIStackView se aplastan, lanzan AutoLayoutadvertencias. Sin embargo, se muestran bien y nada más está mal además de este tipo de registros:

Incapaz de satisfacer simultáneamente las limitaciones. Probablemente, al menos una de las restricciones de la siguiente lista sea una que no desee. Intente esto: (1) observe cada restricción e intente averiguar cuál no espera; (2) encuentre el código que agregó la restricción o restricciones no deseadas y corríjalo. (Nota: si ve NSAutoresizingMaskLayoutConstraintsque no comprende, consulte la documentación de la UIViewpropiedad translatesAutoresizingMaskIntoConstraints) (

Entonces, no estoy seguro de cómo solucionar esto todavía, pero no parece romper nada además de ser molesto.

alguien sabe como resolverlo? Curiosamente, las restricciones de diseño se etiquetan con bastante frecuencia con 'ocultación de UISV' , lo que indica que tal vez debería ignorar los mínimos de altura para las subvistas o algo en este caso.

Ben Guild
fuente
1
Esto parece estar solucionado en iOS11, no recibe ninguna advertencia aquí
trampero

Respuestas:

204

Obtiene este problema porque cuando configura una subvista desde adentro UIStackViewa oculta, primero limitará su altura a cero para animarla.

Recibí el siguiente error:

2015-10-01 11:45:13.732 <redacted>[64455:6368084] Unable to simultaneously satisfy constraints.
    Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 
(
    "<NSLayoutConstraint:0x7f7f5be18c80 V:[UISegmentedControl:0x7f7f5bec4180]-(8)-|   (Names: '|':UIView:0x7f7f5be69d30 )>",
    "<NSLayoutConstraint:0x7f7f5be508d0 V:|-(8)-[UISegmentedControl:0x7f7f5bec4180]   (Names: '|':UIView:0x7f7f5be69d30 )>",
    "<NSLayoutConstraint:0x7f7f5bdfbda0 'UISV-hiding' V:[UIView:0x7f7f5be69d30(0)]>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x7f7f5be18c80 V:[UISegmentedControl:0x7f7f5bec4180]-(8)-|   (Names: '|':UIView:0x7f7f5be69d30 )>

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.

Lo que estaba tratando de hacer era colocar un UIViewdentro de mi UIStackViewque contenía unUISegmentedControl recuadro de 8 puntos en cada borde.

Cuando lo configuro como oculto, intentaría restringir la vista del contenedor a una altura cero, pero debido a que tengo un conjunto de restricciones de arriba a abajo, hubo un conflicto.

Para resolver el problema, cambié mi prioridad de restricciones superior e inferior de 8 puntos de 1000 a 999 para que la UISV-hidingrestricción pueda tener prioridad si es necesario.

liamnichols
fuente
Cabe señalar que parece que si solo tiene una restricción de alto o ancho en estos y reduce su prioridad, no funcionará.
Debe
4
Cambiar las prioridades también funcionó para mí. Además, eliminar las restricciones en exceso (atenuadas) que se copiaron accidentalmente de las clases de tamaño no utilizadas. SUGERENCIA IMPORTANTE: para depurar más fácilmente estos problemas, establezca una cadena IDENTIFER en cada Restricción. Entonces puede ver qué Constraint estaba actuando mal en el mensaje de depuración.
Womble
3
En mi caso, solo tengo que bajar la prioridad a la altura y funciona.
pixelfreak
¡Ese consejo del IDENTIFICADOR es genial! Siempre me he preguntado cómo dar las restricciones en los nombres de los mensajes de depuración, siempre estaba buscando agregar algo a la vista en lugar de la restricción en sí. ¡Gracias @Womble!
Ryan
¡Gracias! La prioridad de 1000 a 999 funcionó. Xcode: Versión 8.3.3 (8E3004b)
Michael Garito
52

Tenía un problema similar que no era fácil de resolver. En mi caso, tenía una vista de pila incrustada en una vista de pila. El UIStackView interno tenía dos etiquetas y un espaciado distinto de cero especificado.

Cuando llame a addArrangedSubview (), automáticamente creará restricciones similares a las siguientes:

V:|[innerStackView]|              | = outerStackView

  V:|[label1]-(2)-[label2]|       | = innerStackView

Ahora, cuando intenta ocultar el innerStackView, obtiene una advertencia de restricciones ambiguas.

Para entender por qué, veamos primero por qué esto no sucede cuando innerStackView.spacinges igual a 0. Cuando llama innerStackView.hidden = true, @liamnichols estaba en lo correcto ... outerStackViewinterceptará mágicamente esta llamada y creará una restricción de ocultación de UISV de0 altura con prioridad 1000 (obligatorio). Presumiblemente, esto es para permitir que los elementos en la vista de pila se animen fuera de la vista en caso de que su código oculto sea llamado dentro de un bloque. Desafortunadamente, no parece haber una forma de evitar que se agregue esta restricción. Sin embargo, no recibirá una advertencia "No se puede satisfacer simultáneamente las restricciones" (USSC), ya que sucede lo siguiente:UIView.animationWithDuration()

  1. la altura de label1 se establece en 0
  2. el espacio entre las dos etiquetas ya estaba definido como 0
  3. la altura de label2 se establece en 0
  4. la altura de innerStackView se establece en 0

Es evidente que se pueden satisfacer esas 4 limitaciones. La vista de pila simplemente suaviza todo en un píxel de altura 0.

Ahora, volviendo al ejemplo con errores, si configuramos el spacingto 2, ahora tenemos estas restricciones:

  1. la altura de label1 se establece en 0
  2. el espacio entre las dos etiquetas fue creado automáticamente por la vista de pila como 2 píxeles de alto con una prioridad de 1000.
  3. la altura de label2 se establece en 0
  4. la altura de innerStackView se establece en 0

La vista de pila no puede tener una altura de 0 píxeles y su contenido una altura de 2 píxeles. Las limitaciones no se pueden satisfacer.

Nota: Puede ver este comportamiento con un ejemplo más simple. Simplemente agregue una UIView a una vista de pila como una subvista organizada. Luego, establezca una restricción de altura en esa UIView con una prioridad de 1000. Ahora intenta llamar a hide en eso.

Nota: Por alguna razón, esto solo sucedió cuando mi vista de pila era una subvista de UICollectionViewCell o UITableViewCell. Sin embargo, aún puede reproducir este comportamiento fuera de una celda llamandoinnerStackView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize) al siguiente ciclo de ejecución después de ocultar la vista de la pila interna.

Nota: Incluso si intenta ejecutar el código en un UIView.performWithoutAnimations, la vista de la pila aún agregará una restricción de altura 0 que provocará la advertencia de USSC.


Hay al menos 3 soluciones para este problema:

  1. Antes de ocultar cualquier elemento en una vista de pila, verifique si es una vista de pila y, de ser así, cambie spacinga 0. Esto es molesto porque necesita revertir el proceso (y recordar el espaciado original) cada vez que muestra el contenido nuevamente.
  2. En lugar de ocultar elementos en una vista de pila, llame a removeFromSuperview. Esto es aún más molesto, ya que cuando invierte el proceso, debe recordar dónde insertar el elemento eliminado. Puede optimizar simplemente llamando a removeArrangedSubview y luego ocultándose, pero hay mucha contabilidad que aún debe hacerse.
  3. Envuelva las vistas de pila anidadas (que no son cero spacing) en una UIView. Especifique al menos una restricción como prioridad no requerida (999 o menos). Esta es la mejor solución, ya que no tiene que llevar la contabilidad. En mi ejemplo, creé restricciones superior, inicial y final en 1000 entre la vista de pila y la vista de envoltura, luego creé una restricción de 999 desde la parte inferior de la vista de pila a la vista de envoltura. De esta manera, cuando la vista de la pila externa crea una restricción de altura cero, la restricción 999 se rompe y no ve la advertencia de USSC. (Nota: Esto es similar a la solución para ¿Debería establecerse el contentView.translatesAutoResizingMaskToConstraints de una subclase UICollectionViewCell enfalse )?

En resumen, las razones por las que obtiene este comportamiento son:

  1. Apple crea automáticamente 1000 restricciones de prioridad cuando agrega subvistas administradas a una vista de pila.
  2. Apple crea automáticamente una restricción de altura 0 para usted cuando oculta una subvista de una vista de pila.

Si Apple (1) le hubiera permitido especificar la prioridad de las restricciones (especialmente de los espaciadores), o (2) le hubiera permitido optar por no participar en la restricción automática de ocultación de UISV , este problema se resolvería fácilmente.

Sentido
fuente
6
Gracias por la explicación muy completa y útil. Sin embargo, esto definitivamente parece un error del lado de Apple con las vistas de pila. Básicamente, su función de "ocultar" es incompatible con su función de "espaciado". ¿Alguna idea de que hayan resuelto esto desde entonces, o han agregado algunas características para evitar piratear con vistas adicionales que contienen? (Nuevamente, gran desglose de posibles soluciones y estoy de acuerdo en la elegancia del n. ° 3)
Marchy
1
¿Todos y cada uno de los UIStackViewniños de una UIStackViewnecesidad deben estar envueltos en un UIViewo solo el que estás buscando ocultar / mostrar?
Adrian
1
Esto se siente como un descuido realmente malo por parte de Apple. Especialmente porque las advertencias y los errores relacionados con el uso de UIStackViewtienden a ser crípticos y difíciles de entender.
bompf
Este es un salvavidas. Tenía un problema en el que UIStackViews agregados a UITableViewCell causaban spam en el registro de errores de AutoLayout, cada vez que se reutilizaba la celda. Incrustar stackView en un UIView con su prioridad de restricción de anclaje inferior establecida en baja, resolvió el problema. Hace que el depurador de vista muestre que los elementos de stackView tienen alturas ambiguas, pero se muestra correctamente en la aplicación, sin spam en el registro de errores. GRACIAS.
Womble
6

La mayoría de las veces, este error se puede resolver reduciendo la prioridad de las restricciones para eliminar conflictos.

Luciano Almeida
fuente
¿Qué quieres decir? Las restricciones son todas relativas dentro de las vistas que se apilan ...
Ben Guild
Lo siento, no entiendo bien su pregunta, mi inglés es regular, pero lo que creo es que las restricciones son relativas a la vista con la que está asociada, si la vista se oculta, las restricciones se vuelven inactivas. No estoy seguro si esa es tu duda, pero espero poder ayudarte.
Luciano Almeida
Parece provenir de cuando las cosas están aplastadas o en el proceso de mostrarse / ocultarse. En este caso, se vuelve parcialmente visible. - ¿Quizás es necesario pasar y eliminar cualquier constante vertical mínima porque se puede aplastar a 0 altura?
Ben Guild
2

Cuando configura una vista como oculta, UIStackviewintentará animarla. Si desea ese efecto, deberá establecer la prioridad correcta para las restricciones para que no entren en conflicto (como muchos han sugerido anteriormente).

Sin embargo, si no le importa la animación (tal vez la esté ocultando en ViewDidLoad), entonces puede simplificar, lo removeFromSuperviewque tendrá el mismo efecto pero sin ningún problema con las restricciones, ya que se eliminarán junto con la vista.

Oren
fuente
1

Según la respuesta de @ Senseful, aquí hay una extensión UIStackView para envolver una vista de pila en una vista y aplicar las restricciones que él o ella recomienda:

/// wraps in a `UIView` to prevent autolayout warnings when a stack view with spacing is placed inside another stack view whose height might be zero (usually due to `hidden` being `true`).
/// See http://stackoverflow.com/questions/32428210
func wrapped() -> UIView {
    let wrapper = UIView()
    translatesAutoresizingMaskIntoConstraints = false
    wrapper.addSubview(self)

    for attribute in [NSLayoutAttribute.Top, .Left, .Right, .Bottom] {
        let constraint = NSLayoutConstraint(item: self,
                                            attribute: attribute,
                                            relatedBy: .Equal,
                                            toItem: wrapper,
                                            attribute: attribute,
                                            multiplier: 1,
                                            constant: 0)
        if attribute == .Bottom { constraint.priority = 999 }
        wrapper.addConstraint(constraint)
    }
    return wrapper
}

En lugar de agregar su stackView, use stackView.wrapped().

Ben Packard
fuente
1

Primero, como han sugerido otros, asegúrese de que las restricciones que puede controlar, es decir, no las restricciones inherentes a UIStackView, estén configuradas con prioridad 999 para que puedan anularse cuando la vista esté oculta.

Si aún experimenta el problema, es probable que el problema se deba al espacio en los StackViews ocultos. Mi solución fue agregar un UIView como espaciador y establecer el espaciado UIStackView en cero. Luego, establezca las restricciones de View.height o View.width (dependiendo de una pila vertical u horizontal) al espaciado de StackView.

A continuación, ajuste las prioridades de resistencia a la compresión de contenido y de acogida de contenido de sus vistas recién agregadas. Es posible que también tenga que cambiar la distribución del StackView principal.

Todo lo anterior se puede hacer en Interface Builder. Además, es posible que deba ocultar / mostrar algunas de las vistas recién agregadas mediante programación para que no tenga espacios no deseados.

Peter Coyle
fuente
1

Recientemente luché con errores de diseño automático al ocultar un archivo UIStackView. En lugar de hacer un montón de libros y envoltorios UIViews, opté por crear una salida para mí parentStackViewy para los niños que quiero ocultar / mostrar.

@IBOutlet weak var parentStackView: UIStackView!
@IBOutlet var stackViewNumber1: UIStackView!
@IBOutlet var stackViewNumber2: UIStackView!

En el guión gráfico, así es como se ve mi parentStack:

ingrese la descripción de la imagen aquí

Tiene 4 niños y cada uno de los niños tiene un montón de vistas de pila dentro de ellos. Cuando oculta una vista de pila, si tiene elementos de IU que también son vistas de pila, verá una secuencia de errores de diseño automático. En lugar de esconderme, opté por eliminarlos.

En mi ejemplo, parentStackViewscontiene una matriz de los 4 elementos: Vista de pila superior, StackViewNumber1, Vista de pila número 2 y Botón de parada. Sus índices en arrangedSubviewsson 0, 1, 2 y 3, respectivamente. Cuando quiero ocultar uno, simplemente lo parentStackView's arrangedSubviewselimino de la matriz. Dado que no es débil, permanece en la memoria y puede volver a colocarlo en el índice deseado más tarde. No lo estoy reinicializando, por lo que se cuelga hasta que se necesita, pero no hincha la memoria.

Entonces, básicamente, puedes ...

1) Arrastre IBOutlets para su pila principal y los elementos secundarios que desea ocultar / mostrar al guión gráfico.

2) Cuando desee ocultarlos, elimine la pila que desea ocultar de la parentStackView's arrangedSubviewsmatriz.

3) Llame self.view.layoutIfNeeded()con UIView.animateWithDuration.

Tenga en cuenta que los dos últimos stackViews no lo son weak. Debes tenerlos cerca para cuando los muestres.

Digamos que quiero ocultar stackViewNumber2:

parentStackView.removeArrangedSubview(stackViewNumber2)
stackViewNumber2.removeFromSuperview()

Entonces anímalo:

UIView.animate(withDuration: 0.25,
               delay: 0,
               usingSpringWithDamping: 2.0,
               initialSpringVelocity: 10.0,
               options: [.curveEaseOut],
               animations: {
                self.view.layoutIfNeeded()
},
               completion: nil)

Si desea "mostrar" stackViewNumber2más tarde, puede insertarlo en el parentStackView arrangedSubViewsíndice deseado y animar la actualización.

parentStackView.removeArrangedSubview(stackViewNumber1)
stackViewNumber1.removeFromSuperview()
parentStackView.insertArrangedSubview(stackViewNumber2, at: 1)

// Then animate it
UIView.animate(withDuration: 0.25,
               delay: 0,
               usingSpringWithDamping: 2.0,
               initialSpringVelocity: 10.0,
               options: [.curveEaseOut],
               animations: {
                self.view.layoutIfNeeded()
},
               completion: nil)

Descubrí que eso es mucho más fácil que llevar la contabilidad de las limitaciones, jugar con las prioridades, etc.

Si tiene algo que desea ocultar de forma predeterminada, puede colocarlo en el guión gráfico y eliminarlo viewDidLoady actualizarlo sin que se use la animación view.layoutIfNeeded().

Adrian
fuente
1

Experimenté los mismos errores con las vistas de pila integradas, aunque todo funcionó bien en tiempo de ejecución.

Resolví los errores de restricción ocultando primero todas las vistas de la subpila (configuración isHidden = true) antes de ocultar la vista de la pila principal.

Hacer esto no tuvo toda la complejidad de eliminar vistas subdispuestas, manteniendo un índice para cuando sea necesario volver a agregarlas.

Espero que esto ayude.

Will Stevens
fuente
1

Senseful ha proporcionado una excelente respuesta a la raíz del problema anterior, así que iré directamente a la solución.

Todo lo que necesita hacer es establecer la prioridad de todas las restricciones de stackView por debajo de 1000 (999 hará el trabajo). Por ejemplo, si stackView está restringido a la izquierda, derecha, arriba y abajo a su supervista, entonces las 4 restricciones deben tener una prioridad menor que 1000.

Linh Ta
fuente
0

Es posible que haya creado una restricción mientras trabajaba con una clase de cierto tamaño (por ejemplo, wCompact hRegular) y luego creó un duplicado cuando cambió a otra clase de tamaño (por ejemplo: wAny hAny). verifique las restricciones de los objetos de la interfaz de usuario en diferentes clases de tamaño y vea si hay anomalías con las restricciones. Debería ver las líneas rojas que indican restricciones en colisión. No puedo poner una imagen hasta que obtenga 10 puntos de reputación, lo siento: /

FM
fuente
Oh ok pero recibí ese error cuando tuve el caso que describí drive.google.com/file/d/0B-mn7bZcNqJMVkt0OXVLVVdnNTA/…
FM
Sí, definitivamente no veo nada rojo al alternar las clases de tamaño en el generador de interfaces. Solo usé el tamaño "Cualquiera".
Ben Guild
0

Quería ocultar todo el UIStackView a la vez, pero estaba obteniendo los mismos errores que el OP, esto lo solucionó:

for(UIView *currentView in self.arrangedSubviews){
    for(NSLayoutConstraint *currentConstraint in currentView.constraints){
        [currentConstraint setPriority:999];
    }
}
valiente
fuente
Esto no funcionó para mí porque el motor de diseño automático se queja cuando cambia una restricción requerida ( priority = 1000) a no requerida ( priority <= 999).
Sentido
0

Tenía una fila de botones con restricción de altura. Esto sucede cuando se oculta un botón. Establecer la prioridad de la restricción de altura de los botones en 999 ha resuelto el problema.

Niklas
fuente
-2

Este error no tiene nada que ver con UIStackView. Ocurre cuando tiene restricciones de conflicto con las mismas prioridades. Por ejemplo, si tiene una restricción indica que el ancho de su vista es 100, y tiene otra restricción al mismo tiempo indica que el ancho de la vista es el 25% de su contenedor. Obviamente, hay dos limitaciones en conflicto. La solución es eliminar uno de ellos.

William Kinaan
fuente
-3

NOP con [mySubView removeFromSuperview]. Espero que pueda ayudar a alguien :)

Grégoire GUYON
fuente
Lo siento, ¿qué es NOP?
Pang