¿Cuándo puedo activar / desactivar las restricciones de diseño?

104

He configurado varios conjuntos de restricciones en IB, y me gustaría alternar programáticamente entre ellos dependiendo de algún estado. Hay una constraintsAcolección de puntos de venta que están marcados como instalados desde IB y una constraintsBcolección de puntos de venta que se desinstalan en IB.

Puedo alternar programáticamente entre los dos conjuntos así:

NSLayoutConstraint.deactivateConstraints(constraintsA)
NSLayoutConstraint.activateConstraints(constraintsB)

Pero ... no sé cuándo hacer eso. Parece que debería poder hacer eso una vez viewDidLoad, pero no puedo hacer que funcione. Intenté llamar view.updateConstraints()y view.layoutSubviews()después de establecer las restricciones, pero fue en vano.

Descubrí que si establezco las restricciones en viewDidLayoutSubviewstodo funciona como se esperaba. Supongo que me gustaría saber dos cosas ...

  1. ¿Por qué tengo este comportamiento?
  2. ¿Es posible activar / desactivar restricciones desde viewDidLoad?
tybro0103
fuente
2
¿Quiere decir que deactivateConstraints y activeConstraints funcionaron en viewWillLayoutSubviews? Intenté eso, y no funcionó allí o en viewDidLoad. En cierto modo funcionó en viewDidAppear; la vista apareció donde deberían ponerla las nuevas restricciones, pero si giraba a horizontal, la vista se movía de regreso a la posición determinada por las restricciones establecidas en IB (y permanecía allí cuando giraba de nuevo a vertical). Al registrar las restricciones, se mostraron las correctas (las que se activaron recientemente). Esto me parece un error.
rdelmar
1
Sí, eran válidos (funcionaban en viewDidAppear), y no hay necesidad de llamar a super porque no hay una implementación predeterminada de viewWillLayoutSubviews (lo intenté con llamar a super de todos modos, pero eso no hizo ninguna diferencia).
rdelmar
1
@rdelmar Acabo de tener la oportunidad de probar más ... Puedo verificar que en realidad obtuve el mismo comportamiento que describiste ... funciona al principio en viewDidAppear, pero luego se revierte con la rotación.
tybro0103
3
Aparentemente, no puede marcar restricciones como no instaladas en IB para este propósito. Encontré esa información aquí: stackoverflow.com/questions/27663249/… y me resolvió el problema.
Stefan
1
Hice implementar mis restricciones de la misma manera que se describe en la pregunta, excepto que activé / desactivé algunas de ellas en viewDidAppear. Esto funcionó, pero podía ver que los elementos cambiaban rápidamente de posición (un problema menor pero indeseable). Hacer el cambio en viewWillAppear o viewDidLoad no funcionó. Pero después de leer esta pregunta, intenté hacer ese cambio en viewDidLayoutSubviews. Funcionó y el cambio de posición ya no es visible para el usuario. (También funcionó en viewWillLayoutSubviews). ¡Gracias por ese consejo!
peacetype

Respuestas:

185

Activo y desactivo NSLayoutConstraintsen viewDidLoad, y no tengo ningún problema con él. Entonces funciona. Debe haber una diferencia en la configuración entre tu aplicación y la mía :-)

Solo describiré mi configuración, tal vez pueda darte una pista:

  1. Configuré @IBOutletstodas las restricciones que necesito activar / desactivar.
  2. En el ViewController, guardo las restricciones en propiedades de clase que no son débiles. La razón de esto es que descubrí que después de desactivar una restricción, no podía reactivarla, era nula. Entonces, parece que se elimina cuando se desactiva.
  3. No uso NSLayoutConstraint.deactivate/activatecomo tú, uso constraint.active = YES/ en su NOlugar.
  4. Después de establecer las limitaciones, llamo view.layoutIfNeeded().
Joachim Bøggild
fuente
131
"guarda las restricciones en propiedades de clase que no sean débiles" ¡Me ahorraste mucho tiempo, gracias!
OpenUserX03
10
"Guardo las restricciones en propiedades de clase que no son débiles": esto me ahorró toneladas de dolor de cabeza. No sabía que estaba llamando a un selector en un objeto nulo. ¡¡Gracias!!
static0886
4
Es importante tener en cuenta que el diseño automático no ignora las restricciones "inactivas", sino que se eliminan. La activación / desactivación de restricciones en realidad las agrega y elimina. Pasé algún tiempo depurando un diseño automático conflictivo después de agregar las restricciones que había establecido previamente .active = falseesperando que fueran ignoradas hasta que las establezca como activas.
lbarbosa
1
guarde las restricciones en propiedades de clase que no sean débiles, de acuerdo, esto ahorra mucho tiempo, obtenía algunos resultados mixtos sin esto. ¡Gracias hombre!
MegaManX
3
Apples doc dice: Activar o desactivar la restricción llama a addConstraint ( :) y removeConstraint ( :) en la vista que es el ancestro común más cercano de los elementos administrados por esta restricción. Utilice esta propiedad en lugar de llamar a addConstraint ( :) o removeConstraint ( :) directamente. Por lo tanto, parece que cuando se desactiva una restricción, se elimina y luego no hay referencias fuertes a la restricción izquierda, a menos que el IBOutlet sea fuerte. Por tanto, se elimina la restricción. En mi humilde opinión, esto es casi un error o al menos un comportamiento muy inesperado.
Olle Raab
52

Tal vez pueda revisar su @properties, reemplazar weakconstrong .

A veces, porque está active = NOconfigurado self.yourConstraint = nil, por lo que no se puede usar de self.yourConstraintnuevo.

León
fuente
5
Como se indica en la Guía de idioma Swift , las propiedades son sólidas de forma predeterminada, por lo que también puede eliminar weaky eso lo hará.
Jonathan Cabrera
30
override func viewDidLayoutSubviews() {
// do it here, after constraints have been materialized
}
Tony Swiftguy
fuente
1
Debido a que mi controlador de vista es un controlador de vista secundario, ¡hacerlo en "didLayoutSubviews" parece ser la única forma en que funcionó! FYI.
TalL
Esta es la única respuesta válida
Yunus Eren Güzel
@TalL ¿Quisiste decir restricciones en el propio controlador de vista secundaria o sus subvistas?
Stefan
este es el mejor
ACAkgul
14

Creo que el problema que está experimentando se debe a que las restricciones no se agregan a sus vistas hasta que viewDidLoad()se llama AFTER . Tienes un número de opciones:

A) Puede conectar sus restricciones de diseño a un IBOutlet y acceder a ellas en su código mediante estas referencias. Dado que los puntos de venta están conectados antes del viewDidLoad()inicio, las restricciones deben ser accesibles y puede continuar activándolas y desactivándolas allí.

B) Si desea utilizar la constraints()función de UIView para acceder a las diversas restricciones, debe esperar a viewDidLayoutSubviews()que se inicie y hacerlo allí, ya que ese es el primer punto después de crear un controlador de vista a partir de una plumilla que tendrá las restricciones instaladas. No olvide llamar layoutIfNeeded()cuando haya terminado. Esto tiene la desventaja de que el pase de diseño se realizará dos veces si hay algún cambio que aplicar y debe asegurarse de que no haya posibilidad de que se active un bucle infinito.

Una advertencia rápida: ¡el método NO devuelve las restricciones deshabilitadas constraints() ! Esto significa que si deshabilita una restricción con la intención de volver a activarla más adelante, deberá mantener una referencia a ella.

C) Puede olvidarse del enfoque del guión gráfico y agregar sus restricciones manualmente. Dado que está haciendo esto en viewDidLoad(), supongo que la intención es hacerlo solo una vez durante toda la vida útil del objeto en lugar de cambiar el diseño sobre la marcha, por lo que este debería ser un método aceptable.

Ceniza
fuente
10

También puede ajustar la prioritypropiedad para "habilitarlos" y "deshabilitarlos" (valor 750 para habilitar y 250 para deshabilitar, por ejemplo). Por alguna razón, cambiar el activeBOOL no tuvo ningún efecto en mi interfaz de usuario. No es necesario layoutIfNeededy se puede configurar y cambiar en viewDidLoad o en cualquier momento posterior.

usuario2387149
fuente
Muy buena sugerencia. Cambiar la prioridad de las restricciones funciona en viewWillTransition(to:, with:)o viewWillLayoutSubviews()y puede mantener todas sus restricciones alternativas como "instaladas" en un guión gráfico. Es posible que la prioridad de la restricción no cambie de no obligatoria a obligatoria, por lo que debe utilizar los valores siguientes 1000. Por otro lado, activar (agregar) y desactivar (eliminar) restricciones solo funciona en viewDidLayoutSubviews()y requiere mantener strong @IBOutletreferencias a NSLayoutConstraint-s.
Gary
"Por alguna razón, cambiar el BOOL activo no tuvo ningún efecto en mi interfaz de usuario". Basado en aquí . Yo creo que no se puede cambiar una restricción con una prioridad 1000 en tiempo de ejecución. Si desea desactivarlo, debe establecer la prioridad inicial en 999 o menos ....
Cariño
No estoy de acuerdo con esta afirmación, ya que puede provocar problemas difíciles de depurar y no responde a la pregunta. Establecer la prioridad en 250 no "desactivará" la restricción, seguirá teniendo efecto e influirá en el diseño. Puede parecer que "desactiva" la restricción en la mayoría de los casos, pero definitivamente no en todos los casos. (en particular, no es el caso que me llevó a encontrar la respuesta a esta pregunta)
Tumata
También puede provocar bloqueos como "No se admite la mutación de una prioridad de obligatoria a no en una restricción instalada (o viceversa). Pasó la prioridad 250 y la prioridad existente era 1000".
Karthick Ramesh
8

El momento adecuado para desactivar las restricciones no utilizadas:

-(void)viewWillLayoutSubviews{
    [super viewWillLayoutSubviews];

    self.myLittleConstraint.active = NO;
}

Tenga en cuenta que viewWillLayoutSubviewspodría llamarse varias veces, por lo que no hay cálculos pesados ​​aquí, ¿de acuerdo?

Nota: si desea reactivar algunas de las restricciones más adelante, almacene siempre la strongreferencia a ellas.

Laszlo
fuente
2
Para mí, la única forma confiable es ajustar las restricciones en viewDidLayoutSubviews(). Ajustar las restricciones en viewWillLayoutSubviews()no funciona en mi caso.
petrsyn
6

Cuando se crea una vista, se llaman en orden los siguientes métodos de ciclo de vida:

  1. loadView
  2. viewDidLoad
  3. viewWillAppear
  4. viewWillLayoutSubviews
  5. viewDidLayoutSubviews
  6. viewDidAppear

Ahora a tus preguntas.

  1. ¿Por qué tengo este comportamiento?

Respuesta: Porque cuando intenta establecer las restricciones en las vistas, viewDidLoadla vista no tiene sus límites, por lo tanto, no se pueden establecer restricciones. Es solo después de viewDidLayoutSubviewsque se finalizan los límites de la vista.

  1. ¿Es posible activar / desactivar restricciones desde viewDidLoad?

Respuesta: No. Razón explicada anteriormente.

Sumeet
fuente
En su descripción del ciclo de vida de viewController, habló sobre cómo se carga primero la vista y luego se llama a viewDidLoad. Sin embargo, también dijo que la vista no se crea en el momento en que se llama a viewDidLoad, esto es claramente una contradicción. Además, puede probar por sí mismo y ver que la vista ha sido creada en el momento en que se llama a viewDidLoad porque puede agregar subvistas a la vista.
ABakerSmith
viewDidLoad debería estar bien ya que las vistas se crean y cargan ... En realidad, donde activa las restricciones se reduce principalmente al rendimiento. Supongo que el problema original no estaba relacionado con el lugar donde se activaron las restricciones. stackoverflow.com/questions/19387998/…
Gabe
@ABakerSmith He editado mi respuesta para que sea más clara.
Sumeet
1

He encontrado siempre que configure las restricciones por normal en la anulación de - (void)updateConstraints(objetivo c), con una strongreferencia para la inicialidad utilizada restricciones activas y no activas. Y en otra parte del ciclo de vista, desactive y / o active lo que necesita, luego llame layoutIfNeeded, no debería tener problemas.

Lo principal es no reutilizar constantemente la anulación updateConstraintsy separar las activaciones de las restricciones, siempre que llame a updateConstraints después de su primera inicialización y diseño. Parece importar después de eso en el ciclo de visualización.

Elliotrock
fuente