¿Cómo puedo reorganizar las vistas al autorizar con autolayout?

Con frecuencia me encuentro con este problema donde quiero que mis vistas estén arregladas de una manera para el retrato y de una manera algo drásticamente diferente para el paisaje.

Para un ejemplo simplificado, considere lo siguiente:

Retrato:

Paisaje:

Observe cómo en el retrato las imágenes se astackn verticalmente, pero en el paisaje, ¿están una al lado de la otra?

Por supuesto, puedo calcular manualmente las posiciones y los tamaños, pero para las vistas más complicadas, esto rápidamente se vuelve complejo. Además, prefiero usar Autolayout para que haya less código específico del dispositivo (si es que hay alguno). Pero eso parece ser mucho código de Autolayout para escribir. ¿Hay alguna forma de configurar las restricciones para este tipo de cosas a través del storyboard?

Hay dos características infrautilizadas del generador de interfaces de Xcode que podemos utilizar aquí para hacer exactamente este tipo de cosas una synchronization.

  1. Puede crear conexiones NSLayoutConstraints para NSLayoutConstraints .
  2. Puede conectar "collections de salida", que es una variedad de objects de IBOutlet .

Entonces, con estas dos cosas en mente, lo esencial de lo que queremos hacer es crear todas nuestras limitaciones de autoajuste para una orientación, conectarlas a todos en una colección de salida. Ahora, para cada una de estas restricciones, desmarca la opción "Instalado" en el generador de interfaces. Luego haga todas las collections de salida para otro layout y conéctelas a otra colección de salida. Podemos crear tantos de estos grupos de layout como deseemos.

Es importante tener en count que necesitaremos una reference a cualquier elemento de interfaz de usuario que tenga restricciones instaladas directamente en él, y necesitaremos una colección de salida separada no solo para cada layout que deseemos, sino también para cada object de interfaz de usuario que tenga restricciones instaladas directamente en él .

Echemos un vistazo al ejemplo bastante simplificado de su pregunta.

introduzca la descripción de la imagen aquí

Si miras en la list de restricciones de la izquierda, puedes ver que la mitad de ellas están atenuadas. Las restricciones atenuadas son las restricciones del paisaje. Creé todos estos, luego desmarcé "Instalado" para cada uno de ellos:

introduzca la descripción de la imagen aquí

Mientras tanto, las restricciones sin apariencia de aspecto normal son las restricciones de retrato. Para estos, los dejé "Instalados". Es completamente innecesario dejar a cualquiera de ellos instalado, pero se encontrará con problemas si deja múltiples sets instalados (es muy probable que entren en conflicto).

introduzca la descripción de la imagen aquí

Asegúrese de no marcar "Eliminar en el momento de compilation" para ninguno de estos. No queremos que las restricciones se "eliminen" en el momento de compilation. Esto simplemente significa que la restricción no se crea en absoluto (por lo que perderemos la reference a ella). Si dejamos esta marca marcada mientras tengamos una IBOutlet a la restricción, Xcode generará una advertencia:

Configuración no admitida
Conexión a la restricción de marcador de position. Las restricciones marcadas como marcadores de position en IB no deben tener conexiones, ya que estas restricciones no se comstackn en el documento y no existirán en el time de ejecución.

De todos modos, ahora tenemos que conectar las restricciones a una salida para que podamos acceder a ellas en time de ejecución.

Mantenga presionada la tecla Ctrl y haga clic y arrastre desde una de las restricciones a su file de código fuente, tal como lo haría con cualquier otro elemento de interfaz de usuario. En el cuadro de dialog que aparece, elija Colección de salida y un nombre descriptivo:

introduzca la descripción de la imagen aquí

Ahora conecte todas las otras restricciones que coincidan con ese grupo de restricciones en la misma colección de salida:

introduzca la descripción de la imagen aquí

Una vez que hayamos terminado de conectar todas nuestras limitaciones, solo será cuestión de eliminarlas o agregarlas en el momento adecuado.

Para un ejemplo tan simple como el escenario descrito en la pregunta, podemos anular updateViewConstraints con un código como este:

Rápido

 class ViewController: UIViewController { @IBOutlet var landscapeConstraints: [NSLayoutConstraint]! @IBOutlet var portraitConstraints: [NSLayoutConstraint]! override func updateViewConstraints() { let isPortrait = self.view.bounds.width < self.view.bounds.height self.view.removeConstraints(self.portraitConstraints) self.view.removeConstraints(self.landscapeConstraints) self.view.addConstraints(isPortrait ? self.portraitConstraints : self.landscapeConstraints) super.updateViewConstraints() } } 

C objective

 @interface ViewController() @property (strong, nonatomic) IBOutletCollection(NSLayoutConstraint) NSArray *landscapeConstraints; @property (strong, nonatomic) IBOutletCollection(NSLayoutConstraint) NSArray *portraitConstraints; @end @implementation ViewController - (void)updateViewConstraints { BOOL isPortrait = self.view.bounds.size.width < self.view.boudns.size.height; [self.view removeConstraints:self.portraitConstraints]; [self.view removeConstraints:self.landscapeConstraints]; [self.view addConstraints:(isPortrait ? self.portraitConstraints : self.landscapeConstraints)]; [super updateViewConstraints]; } @end 

No estamos verificando qué set de restricciones tenía la vista anteriormente, por lo tanto, simplemente elimine ambos sets de restricciones y luego agregue el set apropiado que queremos usar.

Este es todo el código que necesitamos para gestionar por completo el cambio de un set completo de restricciones en un object en time de ejecución. Esto permite trabajar en el generador de interfaces para configurar todas nuestras restricciones en lugar de tener que hacerlo programáticamente (lo cual me parece un poco más tedioso y propenso a errores).

¿El final resulto? Reorderamiento de autoajuste muy bonito sin tirar de tu cabello, consiguiendo que el código de autolayout sea perfecto:

Ahora que UIStackView está disponible en iOS9, una solución más simple y garantizada sería agregar ambas vistas a una vista de stack, vincular la vista de stack a una IBOutlet y luego agregar el siguiente código al viewcontroller:

 - (void) adjustViewsForOrientation:(UIInterfaceOrientation) orientation { if(orientation == UIInterfaceOrientationPortrait || orientation == UIInterfaceOrientationPortraitUpsideDown || orientation == UIInterfaceOrientationUnknown) self.stackView.axis = UILayoutConstraintAxisVertical; else self.stackView.axis = UILayoutConstraintAxisHorizontal; [self.stackView setNeedsLayout]; [self.stackView setNeedsDisplay]; 

}

y llame a este método desde

 -(void) willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration