setNeedsLayout vs setNeedsUpdateConstraints and layoutIfNeeded vs updateConstraintsIfNeeded

227

Sé que la cadena de diseño automático consiste básicamente en 3 procesos diferentes.

  1. actualización de restricciones
  2. vistas de diseño (aquí es donde obtenemos el cálculo de los marcos)
  3. monitor

Lo que no está totalmente claro para mí es la diferencia interna entre -setNeedsLayouty -setNeedsUpdateConstraints. De Apple Docs:

setNeedsLayout

Llame a este método en el hilo principal de su aplicación cuando desee ajustar el diseño de las subvistas de una vista. Este método toma nota de la solicitud y regresa de inmediato. Debido a que este método no fuerza una actualización inmediata, sino que espera el próximo ciclo de actualización, puede usarlo para invalidar el diseño de varias vistas antes de que cualquiera de esas vistas se actualice. Este comportamiento le permite consolidar todas sus actualizaciones de diseño en un ciclo de actualización, que generalmente es mejor para el rendimiento.

setNeedsUpdateConstraints

Cuando una propiedad de su vista personalizada cambia de una manera que afectaría las restricciones, puede llamar a este método para indicar que las restricciones deben actualizarse en algún momento en el futuro. El sistema luego llamará a updateConstraints como parte de su pase de diseño normal. La actualización de todas las restricciones de una vez, justo antes de que sean necesarias, garantiza que no recalcule innecesariamente las restricciones cuando se realizan múltiples cambios en su vista entre los pases de diseño.

Cuando quiero animar una vista después de modificar una restricción y animar los cambios que suelo llamar, por ejemplo:

[UIView animateWithDuration:1.0f delay:0.0f usingSpringWithDamping:0.5f initialSpringVelocity:1 options:UIViewAnimationOptionCurveEaseInOut animations:^{
        [self.modifConstrView setNeedsUpdateConstraints];
        [self.modifConstrView layoutIfNeeded];
    } completion:NULL];

Descubrí que si uso en -setNeedsLayoutlugar de que -setNeedsUpdateConstraintstodo funcione como se esperaba, pero si cambio -layoutIfNeededcon -updateConstraintsIfNeeded, la animación no sucederá.
Intenté llegar a mi propia conclusión:

  • -updateConstraintsIfNeeded solo actualiza las restricciones pero no obliga al diseño a entrar en el proceso, por lo tanto, los marcos originales aún se conserva
  • -setNeedsLayouttambién llama -updateContraintsmétodo

Entonces, ¿cuándo está bien usar uno en lugar del otro? y sobre los métodos de diseño, ¿debo llamarlos en la vista que tiene un cambio en una restricción o en la vista principal?

Andrea
fuente
27
No entiendo a las personas que votan negativamente ... realmente. Así que deberías hacer algo al respecto, como pedir una razón como obligatoria o son totalmente inútiles
Andrea
77
Quizás solo necesiten obtener la insignia de Crítico (primer voto
negativo
1
Le recomiendo que vea aquí . La respuesta es más una solución a un problema real. También vea este video
Miel

Respuestas:

258

Tus conclusiones son correctas. El esquema básico es:

  • setNeedsUpdateConstraintsse asegura de una futura llamada a updateConstraintsIfNeededllamadas updateConstraints.
  • setNeedsLayoutse asegura de una futura llamada a layoutIfNeededllamadas layoutSubviews.

Cuando layoutSubviewsse llama, también llama updateConstraintsIfNeeded, por lo que en mi experiencia rara vez se necesita llamarlo manualmente. De hecho, nunca lo he llamado, excepto al depurar diseños.

setNeedsUpdateConstraintsTambién es bastante raro actualizar las restricciones usando , objc.io, una lectura obligada sobre autolayouts, dice :

Si algo cambia más adelante que invalida una de sus restricciones, debe eliminar la restricción de inmediato y llamar a setNeedsUpdateConstraints. De hecho, ese es el único caso en el que debería activar un pase de actualización de restricciones.

Además, en mi experiencia, nunca he tenido que invalidar restricciones, y no establecerlas setNeedsLayouten la siguiente línea del código, porque las nuevas restricciones están pidiendo un nuevo diseño.

Las reglas generales son:

  • Si manipulaste las restricciones directamente, llama setNeedsLayout.
  • Si ha cambiado algunas condiciones (como compensaciones o algo bajo), que sería cambiar las limitaciones en su reemplazado updateConstraintsmétodo (un método recomendado a las restricciones de cambio, por cierto), llamada setNeedsUpdateConstraints, y la mayoría de las veces, setNeedsLayoutdespués de eso.
  • Si necesita alguna de las acciones anteriores para tener un efecto inmediato, por ejemplo, cuando necesita aprender una nueva altura de cuadro después de un pase de diseño, agréguelo con a layoutIfNeeded.

Además, en su código de animación, creo que no setNeedsUpdateConstraintses necesario, ya que las restricciones se actualizan antes de la animación manualmente, y la animación solo vuelve a mostrar la vista en función de las diferencias entre las antiguas y las nuevas.

encubrimiento
fuente
@coverback, por lo que objc.io dice "Si algo cambia más adelante que invalida una de sus restricciones, debe eliminar la restricción de inmediato y llamar a setNeedsUpdateConstraints. De hecho, ese es el único caso en el que debería activar un pase de actualización de restricción". Y luego, en el bloque Animación, dice que cuando elimino, agrego o cambio restricción.contanto tengo que llamar a setNeedsLayout. ¿Cual es la diferencia? Me siento realmente estúpido :(
pash3r
3
@ pash3r La diferencia es que la actualización constante no califica como "invalidación". La invalidación es cuando ya no es relevante, como si tuviera que adjuntarse a otra vista o eliminarse por completo. Constant simplemente colocaría una vista más cerca o más lejos, o cambiaría su tamaño, de ahí la necesidad setNeedsLayout.
coverback
@coverback setNeedsLayoutse asegura de layoutSubviewsque se llamará en el próximo ciclo de actualización, pero ¿quizás esto no tenga nada que ver layoutIfNeeded?
fujianjin6471
2
@coverback Si manipulas restricciones directamente, se layoutSubviewste llamará automáticamente, no es necesario llamarsetNeedsLayout
fujianjin6471
Sí, manipular las propiedades de una restricción directamente se activará layoutSubviews, por lo que no es necesario hacerlo manualmente. Sin embargo, debe llamar layoutIfNeededsi necesita que los cambios surtan efecto de inmediato en lugar del próximo ciclo de diseño
Charlie Martin
89

La respuesta del coverback es bastante correcta. Sin embargo, me gustaría agregar algunos detalles adicionales.

A continuación se muestra el diagrama de un ciclo típico de UIView que explica otros comportamientos:

Ciclo de vida de UIView

  1. Descubrí que si uso en -setNeedsLayoutlugar de que -setNeedsUpdateConstraintstodo funcione como se esperaba, pero si cambio -layoutIfNeededcon -updateConstraintsIfNeeded, la animación no sucederá.

updateConstraintsnormalmente no hace nada. Simplemente resuelve las restricciones, no las aplica hasta que layoutSubviewsse llama. Entonces la animación requiere una llamada a layoutSubviews.

  1. setNeedsLayout llama también al método -updateContraints

No, esto no es necesario. Si sus restricciones no se han modificado, UIView se saltará la llamada updateConstraints. Debe llamar explícitamente setNeedsUpdateConstraintpara modificar las restricciones en el proceso.

Para llamar updateConstraintsnecesita hacer lo siguiente:

[view setNeedsUpdateConstraints];
[view setNeedsLayout]; 
[view layoutIfNeeded];
Kunal Balani
fuente
Gracias, esto resolvió mi problema. Tenía una UIWindow sin UIView principal que tenía restricciones temporales agregadas al llamar a LayoutIfNeeded () antes de una animación. Agregar un contenedor de subvista a la UIWindow y llamar a estos tres métodos solucionó mi problema.
masterwok
No creo que llamar a layoutIfNeeded justo después de setNeedsLayout sea correcto. Porque los métodos hacen lo mismo a pesar del hecho de que uno está causando que el diseño se vuelva a dibujar inmediatamente y el segundo en un próximo ciclo de actualización.
fillky