¿Qué es NSLayoutConstraint “UIView-Encapsulated-Layout-Height” y cómo debo obligarlo a recalcular de manera limpia?

262

Tengo un sistema UITableViewoperativo con iOS 8 y estoy usando alturas de celda automáticas a partir de restricciones en un guión gráfico.

Una de mis celdas contiene una sola UITextViewy la necesito para contraer y expandir según la entrada del usuario: toque para reducir / expandir el texto.

Estoy haciendo esto agregando una restricción de tiempo de ejecución a la vista de texto y cambiando la constante en la restricción en respuesta a los eventos del usuario:

-(void)collapse:(BOOL)collapse; {

    _collapsed = collapse;

    if(collapse)
        [_collapsedtextHeightConstraint setConstant: kCollapsedHeight]; // 70.0
    else
        [_collapsedtextHeightConstraint setConstant: [self idealCellHeightToShowFullText]];

    [self setNeedsUpdateConstraints];

}

Cuando hago esto, lo envuelvo en tableViewactualizaciones y llamo [tableView setNeedsUpdateConstraints]:

[tableView beginUpdates];

[_briefCell collapse:!_showFullBriefText];

[tableView setNeedsUpdateConstraints];
// I have also tried 
// [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationTop];
// with exactly the same results.

[tableView endUpdates];

Cuando hago esto, mi celda se expande (y se anima mientras lo hace) pero recibo una advertencia de restricciones:

2014-07-31 13:29:51.792 OneFlatEarth[5505:730175] 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:0x7f94dced2b60 V:[UITextView:0x7f94d9b2b200'Brief text: Lorem Ipsum i...'(388)]>",

    "<NSLayoutConstraint:0x7f94dced2260 V:[UITextView:0x7f94d9b2b200'Brief text: Lorem Ipsum i...']-(15)-|   (Names: '|':UITableViewCellContentView:0x7f94de5773a0 )>",

    "<NSLayoutConstraint:0x7f94dced2350 V:|-(6)-[UITextView:0x7f94d9b2b200'Brief text: Lorem Ipsum i...']   (Names: '|':UITableViewCellContentView:0x7f94de5773a0 )>",

    "<NSLayoutConstraint:0x7f94dced6480 'UIView-Encapsulated-Layout-Height' V:[UITableViewCellContentView:0x7f94de5773a0(91)]>"
 )

Will attempt to recover by breaking constraint 

<NSLayoutConstraint:0x7f94dced2b60 V:[UITextView:0x7f94d9b2b200'Brief text: Lorem Ipsum i...'(388)]>

388 es mi altura calculada, las otras restricciones UITextViewson mías de Xcode / IB.

El último me está molestando, supongo que esa UIView-Encapsulated-Layout-Heightes la altura calculada de la celda cuando se renderiza por primera vez (configuro mi UITextViewaltura como> = 70.0), sin embargo, no parece correcto que esta restricción derivada anule un Cnstraint de usuario actualizado.

Peor aún, aunque el código de diseño dice que está tratando de romper mi restricción de altura, no lo hace; continúa recalculando la altura de la celda y todo se dibuja como quisiera.

Entonces, ¿qué es NSLayoutConstraint UIView-Encapsulated-Layout-Height(supongo que es la altura calculada para el tamaño de celda automático) y cómo debo forzarlo a que vuelva a calcular limpiamente?

Rog
fuente
3
cross publicado en los foros de desarrollo de Apple: devforums.apple.com/thread/238803
Rog
3
Resolví un problema similar de la siguiente manera y funciona en iOS 7/8. 1) Baje una de las prioridades de restricción a 750. Intentaría el 1er o el 2do 2) En la subclase de celda en el conjunto awakeFromNib self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight;. Creo que establecer inicialmente la máscara de tamaño automático evita que se agregue la última restricción. Encontré esta solución aquí: github.com/wordpress-mobile/WordPress-iOS/commit/…
Jesse
3
@RogerNolan, ¿alguna noticia? Encontré el mismo problema al jugar con el diseño automático en Interface Builder. Algunas células causan este problema, otras no.
orkenstein
3
No creo que agregar una marca de tamaño automático sea una buena solución.
Rog
1
@RogerNolan, ¿está generando esta celda en IB antes de realizar estos cambios de diseño? He estado depurando el mismo problema pero no estaba agregando restricciones adicionales. Logré suprimir la advertencia reconstruyendo mi vista desde cero, y cuando diferencié los 2 archivos del guión gráfico, la única diferencia era que la versión con las advertencias no tenía la línea <rect key="frame" x="0.0" y="0.0" width="600" height="110"/>en su definición, lo que me hizo creer que se trataba de un error de IB . Al menos el mío era de todos modos.
Ell Neal

Respuestas:

301

Intente reducir la prioridad de su _collapsedtextHeightConstrainta 999. De esa manera, la UIView-Encapsulated-Layout-Heightrestricción proporcionada por el sistema siempre tiene prioridad.

Se basa en lo que regresas -tableView:heightForRowAtIndexPath:. Asegúrese de devolver el valor correcto y su propia restricción, y la generada debe ser la misma. La prioridad más baja para su propia restricción solo es necesaria temporalmente para evitar conflictos mientras las animaciones de colapso / expansión están en vuelo.

Ortwin Gentz
fuente
74
Eso lograría exactamente lo contrario de lo que quiero. UIView-Encapsulated-Layout-Height es incorrecto: pertenece al diseño anterior.
Rog
77
La UIView-Encapsulated-Layout-Heightrestricción se añade por UITableView una vez que se determinan las alturas. systemLayoutSizeFittingSizeCalculo la altura en función de la contentView. Aquí, el UIView-Encapsulated-Layout-Heightno importa. Luego, tableView establece contentSize explícitamente en el valor devuelto por heightForRowAtIndexPath:. En este caso, es correcto reducir la prioridad de nuestras restricciones personalizadas porque la restricción tableView debe tener prioridad después de que se calculen los altos de fila.
Ortwin Gentz
8
@OrtwinGentz: Todavía no entiendo el punto correcto de reducir la prioridad de nuestras restricciones personalizadas porque la restricción tableView debe tener prioridad después de que se calculen los altos de fila . La cosa es que UIView-Encapsulated-Layout-Heightestá mal si no disminuyo la prioridad ...
probando el
38
Sostengo que eso evita el conflicto y no soluciona el problema real, aunque como esto todavía se siente como un error de Apple, creo que probablemente sea lo correcto. Especialmente a la luz del hecho de que Apple recalcula esta restricción y todo se presenta correctamente después de que se imprime el error.
Rog
9
-1 para esta respuesta, ya que supone que la restricción que se agrega es correcta. Lo agregado UIView-Encapsulated-Layout-Widthen mi caso es simplemente incorrecto, pero parece preferirse a mis restricciones explícitas en tiempo de ejecución.
rayo
68

Tengo un escenario similar: una vista de tabla con una celda de fila, en la que hay algunas líneas de objetos UILabel. Estoy usando iOS 8 y autolayout.

Cuando roté, obtuve la altura de fila calculada por el sistema incorrecto (43.5 es mucho menor que la altura real). Parece que:

"<NSLayoutConstraint:0x7bc2b2c0 'UIView-Encapsulated-Layout-Height' V:[UITableViewCellContentView:0x7bc37f30(43.5)]>"

No es solo una advertencia. El diseño de mi celda de vista de tabla es terrible: todo el texto se superpone en una línea de texto.

Me sorprende que la siguiente línea "solucione" mi problema mágicamente (la reproducción automática no se queja y obtengo lo que espero en la pantalla):

myTableView.estimatedRowHeight = 2.0; // any number but 2.0 is the smallest one that works

con o sin esta línea:

myTableView.rowHeight = UITableViewAutomaticDimension; // by itself this line only doesn't help fix my specific problem
Pulgar dorado
fuente
8
¡Finalmente! Esta es una respuesta correcta. En la sesión de WWDC, se mencionó que si va a usar el tamaño automático de altura de fila, deberá establecer un estimado de altura de fila o suceden cosas malas. (Sí, Apple sucedieron cosas realmente desagradables)
Abdalrahman Shatou
50
FWIW, agregar la estimación no ha hecho ninguna diferencia para mí.
Benjohn
3
Je Volví a esta misma respuesta nuevamente, y fui a implementarla con alegría y esperanza. Una vez más, no ha hecho ninguna diferencia para mí :-)
Benjohn
3
Estimaciones devueltas por tableView: estimadoHeightForRowAtIndexPath: DEBE ser al menos tan grande como la celda. De lo contrario, la altura calculada de la tabla será menor que la altura real, y la tabla puede desplazarse hacia arriba (por ejemplo, después de desenrollar segue regresa a la tabla). UITableViewAutomaticDimension no debe usarse.
Matt
1
Tenga en cuenta que cuanto más bajo, estimatedRowHeightmás a menudo cellForRowAtIndexPathse llamará inicialmente. altura de la vista de tabla dividida por los tiempos estimados de RowHeight, para ser exactos. En un iPad Pro de 12 pulgadas, esto puede ser un número de miles y afectará la fuente de datos y puede provocar un retraso significativo.
Mojo66
32

Pude hacer que la advertencia desapareciera especificando una prioridad en uno de los valores en la restricción que los mensajes de advertencia dicen que tenía que romperse (a continuación "Will attempt to recover by breaking constraint"). Parece que mientras establezca la prioridad en algo mayor que 49, la advertencia desaparece.

Para mí, esto significaba cambiar mi restricción, la advertencia decía que intentaba romper:

@"V:|[contentLabel]-[quoteeLabel]|"

a:

@"V:|-0@500-[contentLabel]-[quoteeLabel]|"

De hecho, puedo agregar una prioridad a cualquiera de los elementos de esa restricción y funcionará. No parece importar cuál. Mis celdas terminan a la altura adecuada y no se muestra la advertencia. Roger, por ejemplo, intenta agregar @500justo después de la 388restricción del valor de altura (por ejemplo 388@500).

No estoy completamente seguro de por qué esto funciona, pero he investigado un poco. En la enumeración NSLayoutPriority , parece que el NSLayoutPriorityFittingSizeCompressionnivel de prioridad es 50. La documentación para ese nivel de prioridad dice:

Cuando envía un mensaje de tamaño apropiado a una vista, se calcula el tamaño más pequeño que sea lo suficientemente grande para el contenido de la vista. Este es el nivel de prioridad con el que la vista quiere ser lo más pequeña posible en ese cálculo. Es bastante bajo. En general, no es apropiado hacer una restricción exactamente con esta prioridad. Quieres ser más alto o más bajo.

La documentación para el fittingSizemensaje referenciado dice:

El tamaño mínimo de la vista que satisface las restricciones que posee. (solo lectura)

AppKit establece esta propiedad en el mejor tamaño disponible para la vista, teniendo en cuenta todas las restricciones que posee y sus subvistas y satisface una preferencia para que la vista sea lo más pequeña posible. Los valores de tamaño en esta propiedad nunca son negativos.

No he cavado más allá de eso, pero parece tener sentido que esto tenga algo que ver con el problema.

Jeff Bowen
fuente
Eso es Jeff. Todavía se siente como un error para mí. Sin embargo, Apple no ha respondido al radar :-(
Rog
13

El 99.9% del tiempo, al usar celdas o encabezados personalizados, todos los conflictos UITableViewsocurren cuando la tabla se carga por primera vez. Una vez cargado, normalmente no volverá a ver el conflicto.

Esto sucede porque la mayoría de los desarrolladores generalmente usan una altura fija o una restricción de anclaje de algún tipo para diseñar un elemento en la celda / encabezado. El conflicto ocurre porque cuando la UITableViewprimera carga / se presenta, establece la altura de sus celdas en 0. Esto obviamente entra en conflicto con sus propias restricciones. Para resolver esto, simplemente establezca cualquier restricción de altura fija a una prioridad más baja ( .defaultHigh). Lea atentamente el mensaje de la consola y vea qué restricción decidió romper el sistema de diseño. Por lo general, este es el que necesita cambiar su prioridad. Puede cambiar la prioridad de esta manera:

let companyNameTopConstraint = companyNameLabel.topAnchor.constraint(equalTo: companyImageView.bottomAnchor, constant: 15)
    companyNameTopConstraint.priority = .defaultHigh

NSLayoutConstraint.activate([
            companyNameTopConstraint,
           the rest of your constraints here
            ])
ESTÁS ENOJADO
fuente
1
Hermosa explicación
Glenn
12

Yo era capaz de resolver este error mediante la eliminación de una espuria cell.layoutIfNeeded()que tuve en mi tableView's cellForRowAtmétodo.

Clifton Labrum
fuente
1
Sí, esto resolvió el problema para mí también. Estaba haciendo restricciones de diseño de código, así que inicialmente pensé que podría perder algo. Gracias
John
1
¡La misma cosa! ¡Gracias!
Andrey Chernukha
7

En lugar de informar a la vista de tabla para actualizar sus restricciones, intente volver a cargar la celda:

[tableView beginUpdates];

[_briefCell collapse:!_showFullBriefText];

[tableView reloadRowsAtIndexPaths:@[[tableView indexPathForCell:_briefCell]] withRowAnimation:UITableViewRowAnimationNone];

[tableView endUpdates];

UIView-Encapsulated-Layout-Height es probablemente la altura que la vista de tabla calculó para la celda durante la carga inicial, según las restricciones de la celda en ese momento.

Austin
fuente
Debería haber dicho en mi pregunta, he intentado esto y no funciona. Estoy de acuerdo con su suposición sobre UIView-Encapsulated-Layout-Height
Rog
66
Tenga la recompensa de todos modos por al menos enviar una respuesta. Parece que SO simplemente lo dejará evaporarse de lo contrario.
Rog
6

Otra posibilidad:

Si usa el diseño automático para calcular la altura de la celda (altura de contentView, la mayoría de las veces como se muestra a continuación), y si tiene un separador de vista uitable, debe agregar la altura del separador para obtener la altura de la celda. Una vez que obtenga la altura correcta, no tendrá esa advertencia de auto-distribución.

- (CGFloat)calculateHeightForConfiguredSizingCell:(UITableViewCell *)sizingCell {
   [sizingCell setNeedsLayout];
   [sizingCell layoutIfNeeded];
   CGSize size = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
   return size.height; // should + 1 here if my uitableviewseparatorstyle is not none

}
Cullen SUN
fuente
Eso me ayudó en un caso en el que obtuve ambigüedades de altura de diseño en un diseño de celda bastante complejo solo en algunos simuladores (iPad 6 Plus). Me parece que debido a algunos errores de redondeo interno, el contenido está un poco exprimido y si las restricciones no están preparadas para exprimirse, tengo ambigüedades. Así que en lugar de volver UITableViewAutomaticDimensionen heightForRowAtIndexPathvuelvo [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize] + 0.1
Leo
Quiero decir, por supuesto[sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height + 0.1
Leo
Asombrosamente; quitar el separador es lo que hizo que mi mesa se comportara, así que gracias.
Royalmurder
5

Como mencionó el comentario de Jesse en cuestión, esto funciona para mí:

self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight;

FYI, este problema no ocurre en iOS 10.

Phu Nguyen
fuente
2
En Swift 4.2: self.contentView.autoresizingMask = [.flexibleHeight]
emitido el
4

Tuve este error al usar UITableViewAutomaticDimension y cambiar una restricción de altura en una vista dentro de la celda.

Finalmente descubrí que se debía a que el valor constante de restricción no se había redondeado al entero más cercano.

let neededHeight = width / ratio // This is a CGFloat like 133.2353
constraintPictureHeight.constant = neededHeight // Causes constraint error
constraintPictureHeight.constant = ceil(neededHeight) // All good!
Che
fuente
2
Este fue el comentario que me dio pistas sobre mi problema. Tengo una celda de imagen que se carga dinámicamente (crece y se reduce) y una vista de tabla con estimadoRowHeight = 50 y rowHeight = UITableViewAutomaticDimension. Todavía estaba rompiendo las restricciones a pesar de que la vista de la mesa tenía la altura correcta. Resulta que los separadores tenían una altura de 0.3333 y eso fue lo que pateó la restricción del tamaño de mi imagen en la celda que se rompería. Después de apagar los separadores, todo estaba bien. Gracias Che por darme qué buscar.
migs647
1
En este caso, cree una restricción adicional, por ejemplo, bottomMargin> = view.bottomMargin+1@900. AutoLayout intenta acomodar el 1 punto adicional, cambia el tamaño de la celda, se confunde debido a la altura del separador, intenta romper / relajar algunas restricciones, encuentra el @ 900 y lo descarta. Obtiene el diseño que desea sin advertencias.
Anton
salva mi dia pocas horas de todos modos
Anton Tropashko
1

Cambiar el tamaño de la vista de texto para que se ajuste a su contenido y actualizar la constante de restricción de altura a la altura resultante, arregló el UIView-Encapsulated-Layout-Heightconflicto de restricción para mí, por ejemplo:

[self.textView sizeToFit];
self.textViewHeightConstraint.constant = self.textView.frame.size.height;
Kim André Sand
fuente
¿Dónde hiciste esto? En diseño ¿Subvistas?
stuckj
1

Después de pasar algunas horas rascándome la cabeza con este error, finalmente encontré una solución que me funcionó. mi principal problema era que tenía múltiples puntas registradas para diferentes tipos de células, pero se permitió específicamente que un tipo de célula tuviera diferentes tamaños (no todas las instancias de esa célula serán del mismo tamaño). Entonces, el problema surgió cuando la vista de tabla intentaba eliminar una celda de ese tipo y tenía una altura diferente. Lo resolví configurando

self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight; self.frame = CGRectMake(0, 0, self.frame.size.width, {correct height});

cada vez que la celda tenía sus datos para calcular su tamaño. Me imagino que puede estar en

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

algo como

cell.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight; cell.frame = CGRectMake(0, 0, self.frame.size.width, {correct height});

¡Espero que esto ayude!

Alcides Eduardo Zelaya
fuente
0

TableView obtiene la altura de la celda en indexPath del delegado. luego obtener celular de cellForRowAtIndexPath:

top (10@1000)
    cell
bottom (0@1000)

if cell.contentView.height: 0 // <-> (UIView-Encapsulated-Layout-Height: 0 @ 1000) top (10 @ 1000) en conflicto con (UIView-Encapsulated-Layout-Height: 0 @ 1000),

debido a que las prioridades son iguales a 1000. Necesitamos establecer la máxima prioridad bajo UIView-Encapsulated-Layout-Heightla prioridad de.

usuario2962814
fuente
0

Estaba recibiendo un mensaje como este:

Incapaz de satisfacer simultáneamente las restricciones ...
...
...
...
NSLayoutConstraint: 0x7fe74bdf7e50 'UIView-Encapsulated-Layout-Height' V: [UITableViewCellContentView: 0x7fe75330c5c0 (21.5)]
...
...
intentará recuperarse mediante restricción de ruptura NSLayoutConstraint: 0x7fe0f9b200c0 UITableViewCellContentView: 0x7fe0f9b1e090.bottomMargin == UILabel: 0x7fe0f9b1e970.bottom

Estoy usando una costumbre UITableViewCellcon UITableViewAutomaticDimensionla altura. Y también he implementado el estimatedHeightForRowAtIndex:método.

La restricción que me estaba dando problemas se parecía a esto

[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[title]-|" options:0 metrics:nil views:views];

Cambiar la restricción a esto solucionará el problema, pero como otra respuesta, sentí que esto no era correcto, ya que reduce la prioridad de una restricción que quiero que se requiera:

[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-6@999-[title]-6@999-|" options:0 metrics:nil views:views];

Sin embargo, lo que noté es que si en realidad solo elimino la prioridad, esto también funciona y no obtengo los registros de restricción de ruptura:

[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-6-[title]-6-|" options:0 metrics:nil views:views];

Esto es un misterio en cuanto a cuál es la diferencia entre |-6-[title]-6-|y |-[title-|. Pero especificar el tamaño no es un problema para mí y elimina los registros, y no necesito reducir la prioridad de mis restricciones requeridas.

usuario1687195
fuente
0

Establecer esto view.translatesAutoresizingMaskIntoConstraints = NO;debería resolver este problema.

kathy zhou
fuente
Esto funcionó perfectamente para mí, incluso si no era la solución perfecta.
Enkha
0

Tuve un problema similar con una celda de vista de colección.

Lo resolví bajando la prioridad de la restricción final que estaba vinculada a la parte inferior de la celda (la última en la cadena desde la parte superior a la parte inferior de la vista; esto es, en última instancia, lo que determina su altura) a 999.

La altura de la celda era correcta y las advertencias desaparecieron.

Rico
fuente