Comment puis-je réorganiser les vues lors d'une autorotation avec autolayout?

Je rencontre souvent ce problème où je veux que mes vues soient arrangées d'une manière pour le portrait et d'une manière quelque peu radicalement différente pour le paysage.

Pour un exemple simplifié, considérez ce qui suit:

Portrait:

Paysage:

Remarquez comment, en mode portrait, les images sont empilées verticalement, mais en mode paysage, elles sont côte à côte?

Bien sûr, je peux calculer manuellement les positions et les tailles, mais pour des vues plus compliquées, cela devient rapidement complexe. De plus, je préférerais utiliser Autolayout pour qu'il y ait less de code spécifique à l'appareil (le cas échéant). Mais cela semble être beaucoup de code Autolayout à écrire. Existe-t-il un moyen de mettre en place les contraintes pour ce genre de choses à travers le storyboard?

Il y a deux fonctionnalités sous-utilisées du constructor d'interface de Xcode que nous pouvons utiliser ici pour faire exactement ce genre de chose une synchronisation.

  1. Vous pouvez créer des connections NSLayoutConstraints pour NSLayoutConstraints .
  2. Vous pouvez connecter des "collections de IBOutlet ", qui sont un tableau d'objects IBOutlet .

Donc, avec ces deux choses à l'esprit, l'essentiel de ce que nous voulons faire est de créer toutes nos contraintes autolayout pour une orientation, les accrocher tous dans une collection de sortie. Maintenant, pour chacune de ces contraintes, décochez l'option "Installed" sur le constructor de l'interface. Ensuite, créez une autre layout pour toutes nos collections de magasins, et joignez-les à une autre collection de magasins. Nous pouvons créer autant de ces groupes de disposition que nous le voulons.

Il est important de noter que nous aurons besoin d'une reference à tout élément de l'interface user qui a directement des contraintes, et nous aurons besoin d'une collection séparée non seulement pour chaque layout que nous voulons, mais pour chaque object UI qui a des contraintes directement .

Jetons un coup d'oeil à l'exemple assez simplifié de votre question.

entrez la description de l'image ici

Si vous regardez dans la list des contraintes sur la gauche, vous pouvez voir que la moitié d'entre eux sont grisés. Les contraintes grisées sont les contraintes du paysage. J'ai créé tout cela, puis décoché "Installé" pour chacun d'entre eux:

entrez la description de l'image ici

Pendant ce time, les contraintes d'apparence normale et non graissées sont les contraintes du portrait. Pour ceux-ci, je les ai laissés "Installés". Il est complètement inutile de laisser l'un d'entre eux installé, mais vous rencontrerez des problèmes si vous laissez plusieurs jeux installés (ils sont très probablement en conflit).

entrez la description de l'image ici

Assurez-vous de ne pas cocher "Supprimer au moment de la compilation" pour l'un de ces éléments. Nous ne voulons pas que les contraintes soient "supprimées" au moment de la construction. Cela signifie simplement que la contrainte n'est pas du tout créée (nous allons donc perdre la reference). Si nous laissons cette vérification marquée alors que nous avons un IBOutlet à la contrainte, Xcode va générer un avertissement:

Configuration non prise en charge
login à la contrainte d'espace réservé. Les contraintes marquées comme espaces réservés dans IB ne doivent pas avoir de connections car ces contraintes ne sont pas compilées dans le document et n'existeront pas au moment de l'exécution.

Quoi qu'il en soit, nous devons maintenant twigr les contraintes à un sharepoint vente afin que nous puissions y accéder au moment de l'exécution.

Maintenez la touche Ctrl enfoncée et cliquez et faites glisser l'une des contraintes vers votre file de code source, comme vous le feriez avec n'importe quel autre élément de l'interface user. Dans la boîte de dialog qui apparaît, choisissez Outlet Collection et un nom descriptif:

entrez la description de l'image ici

Maintenant, connectez toutes les autres contraintes qui correspondent à ce groupe de contraintes dans la même collection de sockets:

entrez la description de l'image ici

Une fois que nous avons terminé toutes nos contraintes, il suffit de les supprimer / les append au moment opportun.

Pour un exemple aussi simple que le scénario décrit dans la question, nous pouvons substituer updateViewConstraints avec du code comme celui-ci:

Rapide

 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() } } 

Objectif c

 @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 

Nous ne vérifions pas quel set de contraintes la vue avait précédemment, il suffit donc de supprimer les deux sets de contraintes, puis d'append l'set approprié que nous voulons utiliser.

C'est tout le code que nous devons gérer en changeant complètement un set de contraintes sur un object au moment de l'exécution. Cela permet de travailler dans le constructor de l'interface pour régler toutes nos contraintes au lieu de devoir le faire par programmation (ce que je trouve un peu plus fastidieux et sujet aux erreurs).

Le résultat final? Très beau réarrangement d'autorotation sans tirer les cheveux pour get le code de l'autolayout parfaitement:

Maintenant que UIStackView est disponible dans iOS9, une solution plus simple et garantie serait d'append les deux vues à une stackview, de lier la stackview à un IBOutlet, puis d'append le code suivant au 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]; 

}

et appelez cette méthode de

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