[EDYCJA: Ostrzeżenie: cała następująca dyskusja zostanie prawdopodobnie przestarzała lub przynajmniej mocno złagodzona przez iOS 8, który może już nie popełniać błędu uruchamiania układu w momencie zastosowania transformacji widoku].
Autoukład a widok przekształceń
Autolayout nie działa dobrze z transformacjami widoku. Powód, o ile mogę stwierdzić, jest taki, że nie należy mieszać w ramkę widoku, który ma transformację (inną niż domyślna transformacja tożsamości) - ale właśnie to robi autoukład. Sposób działania autoukładu polega na tym, że w layoutSubviews
środowisku wykonawczym przechodzi przez wszystkie ograniczenia i odpowiednio ustawia klatki wszystkich widoków.
Innymi słowy, ograniczenia nie są magiczne; to tylko lista rzeczy do zrobienia. layoutSubviews
to miejsce, w którym lista rzeczy do zrobienia jest wykonywana. I robi to poprzez ustawienie ramek.
Nie mogę pomóc w traktowaniu tego jako błędu. Jeśli zastosuję tę transformację do widoku:
v.transform = CGAffineTransformMakeScale(0.5,0.5);
Spodziewam się, że widok pojawi się ze środkiem w tym samym miejscu co poprzednio i będzie o połowę mniejszy. Ale w zależności od ograniczeń może to wcale nie być to, co widzę.
[Właściwie jest tu druga niespodzianka: zastosowanie transformacji do widoku natychmiast uruchamia układ. Wydaje mi się, że to kolejny błąd. A może jest to sedno pierwszego błędu. To, czego bym się spodziewał, to móc uciec z transformacją przynajmniej do czasu rozmieszczenia, np. Urządzenie jest obrócone - tak jak mogę uciec z animacją klatki do czasu rozmieszczenia. Ale w rzeczywistości czas na przygotowanie jest natychmiastowy, co wydaje się po prostu niewłaściwe.]
Rozwiązanie 1: Brak ograniczeń
Jedno z obecnych rozwiązań polega na tym, że jeśli mam zamiar zastosować półtrwałą transformację do widoku (a nie tylko tymczasowo nim poruszać), usunąć wszystkie ograniczenia, które na niego wpływają. Niestety, zazwyczaj powoduje to zniknięcie widoku z ekranu, ponieważ nadal ma miejsce autoukład, a teraz nie ma żadnych ograniczeń, aby powiedzieć nam, gdzie umieścić widok. Więc oprócz usunięcia ograniczeń, ustawiłem widok na translatesAutoresizingMaskIntoConstraints
TAK. Widok działa teraz w stary sposób, bez wpływu autoukładu. (Oczywiście ma na to wpływ autoukład, ale niejawne ograniczenia dotyczące autorezyzacji maski powodują, że jej zachowanie jest takie samo, jak przed autoukładem).
Rozwiązanie 2: Używaj tylko odpowiednich ograniczeń
Jeśli wydaje się to nieco drastyczne, innym rozwiązaniem jest ustawienie ograniczeń, aby działały poprawnie z zamierzoną transformacją. Jeśli rozmiar widoku jest określany wyłącznie na podstawie swojej wewnętrznej stałej szerokości i wysokości i jest umieszczony wyłącznie według jego środka, na przykład moja transformacja skali będzie działać zgodnie z oczekiwaniami. W tym kodzie usuwam istniejące ograniczenia z widoku podrzędnego ( otherView
) i zastępuję je tymi czterema ograniczeniami, nadając mu stałą szerokość i wysokość oraz przypinając go wyłącznie po środku. Po tym moja transformacja skali działa:
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]];
Wynik jest taki, że jeśli nie masz żadnych ograniczeń, które wpływają na ramkę widoku, autoukład nie dotknie ramki widoku - co jest właśnie tym, czego szukasz, gdy zaangażowana jest transformacja.
Rozwiązanie 3: Użyj widoku podrzędnego
Problem z obydwoma powyższymi rozwiązaniami polega na tym, że tracimy korzyści wynikające z ograniczeń pozycjonowania naszego punktu widzenia. Oto rozwiązanie, które to rozwiązuje. Zacznij od niewidocznego widoku, którego zadaniem jest wyłącznie pełnienie roli gospodarza, i użyj ograniczeń, aby go ustawić. Wewnątrz umieść prawdziwy widok jako podgląd podrzędny. Użyj ograniczeń, aby umieścić podgląd podrzędny w widoku nadrzędnym, ale ogranicz te ograniczenia do ograniczeń, które nie będą walczyć, gdy zastosujemy transformację.
Oto ilustracja:
Widok biały to widok hosta; powinieneś udawać, że jest przezroczysty i przez to niewidoczny. Widok czerwony to jego widok podrzędny, umieszczony przez przypięcie jego środka do środka widoku nadrzędnego. Teraz możemy bez problemu skalować i obracać czerwony widok wokół jego środka, a ilustracja rzeczywiście pokazuje, że to zrobiliśmy:
self.otherView.transform = CGAffineTransformScale(self.otherView.transform, 0.5, 0.5);
self.otherView.transform = CGAffineTransformRotate(self.otherView.transform, M_PI/8.0);
W międzyczasie ograniczenia widoku hosta utrzymują go we właściwym miejscu, gdy obracamy urządzenie.
Rozwiązanie 4: Zamiast tego użyj przekształceń warstw
Zamiast przekształceń widoku należy używać przekształceń warstw, które nie powodują wyzwalania układu, a tym samym nie powodują natychmiastowego konfliktu z ograniczeniami.
Na przykład ta prosta animacja widoku „pulsującego” może się zepsuć podczas automatycznego układania:
[UIView animateWithDuration:0.3 delay:0
options:UIViewAnimationOptionAutoreverse
animations:^{
v.transform = CGAffineTransformMakeScale(1.1, 1.1);
} completion:^(BOOL finished) {
v.transform = CGAffineTransformIdentity;
}];
Mimo że ostatecznie nie nastąpiła zmiana rozmiaru widoku, wystarczy ustawić jego transform
układ przyczyn, a ograniczenia mogą sprawić, że widok przeskoczy. (Czy to wygląda na błąd, czy co?) Ale jeśli zrobimy to samo z Core Animation (używając CABasicAnimation i stosując animację do warstwy widoku), układ nie występuje i działa dobrze:
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
znasz jego szerokość? Czy przykleja swoją prawą stronę do czegoś innego?