[MODIFICA: Avviso: l'intera discussione che ne seguirà sarà probabilmente superata o almeno fortemente mitigata da iOS 8, il che potrebbe non commettere più l'errore di innescare il layout nel momento in cui viene applicata una trasformazione della vista.]
Autolayout vs. Visualizza trasformazioni
Il trasferimento automatico non funziona affatto bene con le trasformazioni della vista. Il motivo, per quanto posso discernere, è che non dovresti fare confusione con il frame di una vista che ha una trasformazione (diversa dalla trasformazione dell'identità predefinita), ma è esattamente ciò che fa il payout automatico. Il modo in cui funziona il autolayout è che in layoutSubviews
fase di esecuzione viene eseguito il superamento di tutti i vincoli e l'impostazione dei frame di tutte le viste di conseguenza.
In altre parole, i vincoli non sono magici; sono solo un elenco di cose da fare. layoutSubviews
è dove viene eseguita la lista delle cose da fare. E lo fa impostando i frame.
Non posso fare a meno di considerare questo come un bug. Se applico questa trasformazione a una vista:
v.transform = CGAffineTransformMakeScale(0.5,0.5);
Mi aspetto di vedere la vista apparire con il suo centro nello stesso posto di prima e a metà della dimensione. Ma a seconda dei suoi vincoli, potrebbe non essere quello che vedo affatto.
[In realtà, qui c'è una seconda sorpresa: l'applicazione di una trasformazione a una vista attiva immediatamente il layout. Questo mi sembra un altro bug. O forse è il cuore del primo bug. Quello che mi aspetterei è di riuscire a cavarmela con una trasformazione almeno fino al momento del layout, ad esempio il dispositivo viene ruotato, così come posso cavarmela con un'animazione del frame fino al tempo del layout. Ma in effetti il tempo di layout è immediato, il che sembra sbagliato.]
Soluzione 1: nessun vincolo
Una soluzione attuale è, se ho intenzione di applicare una trasformazione semipermanente a una vista (e non semplicemente agitarla temporaneamente in qualche modo), per rimuovere tutti i vincoli che la riguardano. Sfortunatamente questo in genere fa scomparire la vista dallo schermo, poiché il trasferimento automatico ha ancora luogo e ora non ci sono vincoli per dirci dove posizionare la vista. Quindi oltre a rimuovere i vincoli, ho impostato la vista translatesAutoresizingMaskIntoConstraints
su SÌ. La vista ora funziona alla vecchia maniera, senza essere influenzata dall'autolayout. ( Ovviamente è influenzato dal completamento automatico, ma i vincoli impliciti della maschera di ridimensionamento automatico fanno sì che il suo comportamento sia esattamente come prima del completamento automatico.)
Soluzione 2: utilizzare solo vincoli appropriati
Se questo sembra un po 'drastico, un'altra soluzione è impostare i vincoli affinché funzionino correttamente con una trasformazione prevista. Se una vista viene dimensionata esclusivamente in base alla larghezza e all'altezza fisse interne e posizionata esclusivamente al centro, ad esempio, la trasformazione della scala funzionerà come mi aspetto. In questo codice, rimuovo i vincoli esistenti su una subview ( otherView
) e li sostituisco con quei quattro vincoli, dandogli una larghezza e un'altezza fisse e bloccandoli puramente al centro. Dopodiché, la mia trasformazione di scala funziona:
NSMutableArray* cons = [NSMutableArray array];
for (NSLayoutConstraint* con in self.view.constraints)
if (con.firstItem == self.otherView || con.secondItem == self.otherView)
[cons addObject:con];
[self.view removeConstraints:cons];
[self.otherView removeConstraints:self.otherView.constraints];
[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:self.otherView attribute:NSLayoutAttributeCenterX relatedBy:0 toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1 constant:self.otherView.center.x]];
[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:self.otherView attribute:NSLayoutAttributeCenterY relatedBy:0 toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:self.otherView.center.y]];
[self.otherView addConstraint:
[NSLayoutConstraint constraintWithItem:self.otherView attribute:NSLayoutAttributeWidth relatedBy:0 toItem:nil attribute:0 multiplier:1 constant:self.otherView.bounds.size.width]];
[self.otherView addConstraint:
[NSLayoutConstraint constraintWithItem:self.otherView attribute:NSLayoutAttributeHeight relatedBy:0 toItem:nil attribute:0 multiplier:1 constant:self.otherView.bounds.size.height]];
Il risultato è che se non ci sono vincoli che influenzano il frame di una vista, il layout automatico non toccherà il frame della vista - che è proprio quello che stai cercando quando è coinvolta una trasformazione.
Soluzione 3: utilizzare una vista secondaria
Il problema con entrambe le soluzioni di cui sopra è che perdiamo i vantaggi dei vincoli per posizionare la nostra visione. Quindi, ecco una soluzione che risolve questo. Inizia con una vista invisibile il cui compito è esclusivamente quello di fungere da host e utilizzare i vincoli per posizionarlo. Al suo interno, inserisci la vista reale come sottoview. Utilizzare i vincoli per posizionare la sottoview nella vista host, ma limitare tali vincoli ai vincoli che non reagiranno quando applicheremo una trasformazione.
Ecco un'illustrazione:
La vista bianca è vista host; dovresti far finta che sia trasparente e quindi invisibile. La vista rossa è la sua vista secondaria, posizionata fissando il suo centro al centro della vista host. Ora possiamo ridimensionare e ruotare la vista rossa attorno al suo centro senza alcun problema, e in effetti l'illustrazione mostra che l'abbiamo fatto:
self.otherView.transform = CGAffineTransformScale(self.otherView.transform, 0.5, 0.5);
self.otherView.transform = CGAffineTransformRotate(self.otherView.transform, M_PI/8.0);
E intanto i vincoli sulla vista host lo mantengono nel posto giusto mentre ruotiamo il dispositivo.
Soluzione 4: utilizzare invece le trasformazioni di livello
Invece di visualizzare le trasformazioni, utilizzare le trasformazioni di livello che non attivano il layout e quindi non causano conflitti immediati con i vincoli.
Ad esempio, questa semplice animazione di visualizzazione "pulsata" potrebbe interrompersi con il completamento automatico:
[UIView animateWithDuration:0.3 delay:0
options:UIViewAnimationOptionAutoreverse
animations:^{
v.transform = CGAffineTransformMakeScale(1.1, 1.1);
} completion:^(BOOL finished) {
v.transform = CGAffineTransformIdentity;
}];
Anche se alla fine non vi è stato alcun cambiamento nelle dimensioni della vista, è sufficiente impostarne il transform
layout e i vincoli possono far saltare la vista. (Ti sembra un bug o cosa?) Ma se facciamo la stessa cosa con Core Animation (usando un CABasicAnimation e applicando l'animazione al livello della vista), il layout non accade e funziona bene:
CABasicAnimation* ba = [CABasicAnimation animationWithKeyPath:@"transform"];
ba.autoreverses = YES;
ba.duration = 0.3;
ba.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.1, 1.1, 1)];
[v.layer addAnimation:ba forKey:nil];
layerView
sapere la sua larghezza? Sta attaccando il suo lato destro a qualcos'altro?