2013-07-21 8 views
46

È possibile modificare i vincoli quando il dispositivo viene ruotato? Come si potrebbe ottenere ciò?Posso usare l'autolayout per fornire diversi vincoli per l'orientamento orizzontale e verticale?

Un semplice esempio potrebbe essere due immagini che, in verticale, sono impilate una sopra l'altra, ma in orizzontale sono affiancate.

Se ciò non è possibile, in quale altro modo posso realizzare questo layout?

Sto costruendo le mie viste e vincoli nel codice e non utilizzando il generatore di interfacce.

risposta

39

Modifica: Utilizzando il nuovo concetto di Size Classes introdotto in Xcode 6, è possibile impostare facilmente diversi vincoli per classi di dimensioni specifiche in Interface Builder. La maggior parte dei dispositivi (ad esempio tutti gli iPhone attuali) hanno una classe di dimensioni verticale Compact in modalità orizzontale.

questo è un concetto molto meglio per le decisioni di layout generali di determinare l'orientamento del dispositivo.

Detto questo, se è davvero necessario conoscere l'orientamento, UIDevice.currentDevice().orientation è la strada da percorrere.


Original post:

l'override del metodo di UIViewControllerupdateViewConstraints per fornire i vincoli di layout per situazioni specifiche. In questo modo, il layout è sempre impostato correttamente in base alla situazione. Assicurati che formino un insieme completo di vincoli con quelli creati nello storyboard. È possibile utilizzare IB per impostare i propri vincoli generali e contrassegnare gli oggetti da modificare da rimuovere in fase di runtime.

Io uso la seguente implementazione di presentare un diverso insieme di vincoli per ogni orientamento:

-(void)updateViewConstraints { 
    [super updateViewConstraints]; 

    // constraints for portrait orientation 
    // use a property to change a constraint's constant and/or create constraints programmatically, e.g.: 
    if (!self.layoutConstraintsPortrait) { 
     UIView *image1 = self.image1; 
     UIView *image2 = self.image2; 
     self.layoutConstraintsPortrait = [[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[image1]-[image2]-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(image1, image2)] mutableCopy]; 
     [self.layoutConstraintsPortrait addObject:[NSLayoutConstraint constraintWithItem:image1 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem: image1.superview attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]]; 
     [self.layoutConstraintsPortrait addObject:[NSLayoutConstraint constraintWithItem:image2 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:image2.superview attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]]; 
    } 

    // constraints for landscape orientation 
    // make sure they don't conflict with and complement the existing constraints 
    if (!self.layoutConstraintsLandscape) { 
     UIView *image1 = self.image1; 
     UIView *image2 = self.image2; 
     self.layoutConstraintsLandscape = [[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[image1]-[image2]-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(image1, image2)] mutableCopy]; 
     [self.layoutConstraintsLandscape addObject:[NSLayoutConstraint constraintWithItem:image1 attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:image1.superview attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]]; 
     [self.layoutConstraintsLandscape addObject:[NSLayoutConstraint constraintWithItem:image2 attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem: image2.superview attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]]; 
    } 

    BOOL isPortrait = UIInterfaceOrientationIsPortrait(self.interfaceOrientation); 
    [self.view removeConstraints:isPortrait ? self.layoutConstraintsLandscape : self.layoutConstraintsPortrait]; 
    [self.view addConstraints:isPortrait ? self.layoutConstraintsPortrait : self.layoutConstraintsLandscape];   
} 

Ora, tutto quello che dovete fare è innescare un aggiornamento del vincolo ogni volta che la situazione cambia. Override willAnimateRotationToInterfaceOrientation:duration: per animare l'aggiornamento vincolo sul cambiamento dell'orientamento:

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { 
    [super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration]; 

    [self.view setNeedsUpdateConstraints]; 

} 
+1

Non è necessario manualmente per "attivare" un aggiornamento dei vincoli. Il metodo 'updateViewConstraints' lo fa già. Questo commento è stato fatto anche da rokridi in basso, ma questa risposta non è stata modificata. –

+0

Non dimenticare che il dispositivo potrebbe essere ruotato mentre la vista è in background. Potresti usare 'setNeedsUpdateConstraints' quando l'app è in primo piano. –

+0

Con il rilascio di iOS 8 il concetto di * Size Classes * risolverà comunque questo problema;) – knl

0

È possibile salvare i vincoli, in una proprietà o variabile come versioni verticale e orizzontale, quindi impostarli e rimuoverli ruotando.

Ho fatto questo rendendo i miei vincoli in xib per la vista iniziale, assegnandoli a punti vendita nel controller della vista. A rotazione, creo i fermi alternativi, rimuovi le uscite ma le mantieni, inserisci i supplenti.

Invertire la procedura di rotazione indietro.

+0

Cosa succede se il dispositivo viene ruotato quando l'app è fuori dallo schermo? E ci sono altri casi limite di cui dovrei essere a conoscenza? –

+1

la tua vista non riceverà notifiche di rotazione quando non è visibile. Ne sono abbastanza sicuro. Infatti ricordo di averlo visto in alcuni thread durante la ricerca di alcune soluzioni correlate. Suppongo che avresti bisogno di controllare l'orientamento del dispositivo su viewWillAppear. –

2

sto seguendo lo stesso approccio come il vostro (nessun file pennino o storyboard). È necessario aggiornare i vincoli nel metodo updateViewConstraints (controllando l'orientamento del dispositivo). Non è necessario chiamare setNeedsUpdateConstraints in updateViewConstraints perché non appena si modifica l'orientamento del dispositivo, viene chiamato automaticamente l'ultimo metodo.

16

L'approccio che sto usando (nel bene o nel male) consiste nel definire entrambi i gruppi di vincoli (verticale e orizzontale) nell'editor di storyboard.

Per evitare gli avvertimenti dell'inquadratura dello storyboard, inserisco tutti un set con priorità 999, in modo che non mostri il rosso ovunque.

poi aggiungo tutti i vincoli alla presa di collezioni:

@property (strong, nonatomic) IBOutletCollection(NSLayoutConstraint) NSArray *portraitConstraints; 
@property (strong, nonatomic) IBOutletCollection(NSLayoutConstraint) NSArray *landscapeConstraints; 

Infine, a implementare le mie ViewControllers 's viewWillLayout metodo:

- (void) viewWillLayoutSubviews { 
    [super viewWillLayoutSubviews]; 
    for (NSLayoutConstraint *constraint in self.portraitConstraints) { 
     constraint.active = (UIApplication.sharedApplication.statusBarOrientation == UIDeviceOrientationPortrait); 
    } 
    for (NSLayoutConstraint *constraint in self.landscapeConstraints) { 
     constraint.active = (UIApplication.sharedApplication.statusBarOrientation != UIDeviceOrientationPortrait); 
    } 
} 

Questo sembra funzionare. I davvero vorrei poter impostare la proprietà attiva predefinita nell'editor storyboard.

+0

Molto bello, grazie! Purtroppo devo farlo anche perché sto supportando iOS 7, quindi non posso ancora sfruttare al massimo le classi Size. Ho una sottoview personalizzata e faccio lo stesso che si fa in 'viewWillLayoutSubviews' in un 'updateConstraints' sovrascritto, quindi tutto quello che devo fare è chiamare setNeedsUpdateConstraints da willAnimateRotationToInterfaceOrientation. Funziona bene. – bandejapaisa

+0

Vorrei anche darti un altro segno di spunta per impostare la priorità per evitare avvisi. – bandejapaisa

+1

NOTA: la proprietà attiva è solo iOS 8. Inoltre, pur rilevando questo, ho trovato anche alcuni metodi di classe di convenienza su NSLayoutConstraint.activateConstraints() e NSLayoutConstraint.deactivateConstraints(), in cui è possibile passare in IBOutletCollection. In iOS 7, è quasi la stessa tecnica, ma uso self.removeConstraint e self.addConstraint, che funzionano nel mio caso, ma non so quanto sia fattibile per tutti i casi. – bandejapaisa

-1

La mia idea è di gestire l'orientamento modificando le priorità dei vincoli.
priorità Supponendo saranno:

  • paesaggio: 910 attivo/10 inattivo.
  • ritratto: 920 attivo/20 inattivo.

Fase 1: Creare paesaggio (o verticale) di progettazione in Storyboard con vincoli.
Fase 2: Per i vincoli, che deve essere attivo solo per il paesaggio priorità modalità impostata a 10.
Fase 3: aggiungere vincoli per la modalità portrait e impostare la loro priorità di 920.

In willAnimateRotationToInterfaceOrientation codice aggiuntivo :

for (NSLayoutConstraint *constraint in myView.constraints) { 
    if (UIInterfaceOrientationIsLandscape(UIApplication.sharedApplication.statusBarOrientation)) { 
     if (constraint.priority == 10) constraint.priority = 910; 
     if (constraint.priority == 920) constraint.priority = 20; 
    } else { 
     if (constraint.priority == 20) constraint.priority = 920; 
     if (constraint.priority == 910) constraint.priority = 10; 
    } 
} 

Vantaggio di questo approccio: regolazione semplice in Interface Builder. Quando bisogna passare a qualsiasi orientamento, selezioniamo tutti i vincoli di priorità e cambiare simultaneamente (910-> 10, 20-> 920):

enter image description here

Interface ricostruita automaticamente.

+0

Man. La priorità dei vincoli sembrava consentire di dichiarare i vincoli una volta che l'app li gestiva automaticamente. Ma li usi ancora per controllarli manualmente su ogni azione di rotazione. –

+0

La priorità dei vincoli non deve essere dichiarata una volta e può essere modificata a rotazione. Questo approccio consente di utilizzare meno codice di qualsiasi altro. – Igor