[BEARBEITEN: Warnung: Die gesamte nachfolgende Diskussion wird möglicherweise durch iOS 8 veraltet oder zumindest stark gemildert, was möglicherweise nicht mehr den Fehler macht, das Layout zum Zeitpunkt der Anwendung einer Ansichtstransformation auszulösen.]
Autolayout vs. View-Transformationen
Autolayout spielt bei Ansichtstransformationen überhaupt nicht gut. Der Grund ist, soweit ich das beurteilen kann, dass Sie sich nicht mit dem Rahmen einer Ansicht herumschlagen sollen, die eine Transformation enthält (außer der Standardidentitätstransformation) - aber genau das macht Autolayout. Die Art und Weise, wie Autolayout funktioniert, besteht darin, dass layoutSubviews
zur Laufzeit alle Einschränkungen durchlaufen und die Frames aller Ansichten entsprechend festgelegt werden.
Mit anderen Worten, die Einschränkungen sind keine Magie; Sie sind nur eine To-Do-Liste. layoutSubviews
Hier wird die To-Do-Liste erstellt. Und das durch Setzen von Frames.
Ich kann nicht anders, als dies als Fehler zu betrachten. Wenn ich diese Transformation auf eine Ansicht anwende:
v.transform = CGAffineTransformMakeScale(0.5,0.5);
Ich erwarte, dass die Ansicht mit ihrer Mitte an derselben Stelle wie zuvor und in halber Größe angezeigt wird. Aber je nach den Einschränkungen sehe ich das möglicherweise überhaupt nicht.
[Eigentlich gibt es hier eine zweite Überraschung: Das Anwenden einer Transformation auf eine Ansicht löst sofort das Layout aus. Dies scheint mir ein weiterer Fehler zu sein. Oder vielleicht ist es das Herzstück des ersten Fehlers. Was ich erwarten würde, ist, dass ich zumindest bis zur Layoutzeit mit einer Transformation davonkommen kann, z. B. wenn das Gerät gedreht wird - genauso wie ich mit einer Rahmenanimation bis zur Layoutzeit davonkommen kann. Tatsächlich ist die Layoutzeit jedoch unmittelbar, was einfach falsch erscheint.]
Lösung 1: Keine Einschränkungen
Eine aktuelle Lösung besteht darin, alle Einschränkungen, die sie betreffen, zu entfernen, wenn ich eine semipermanente Transformation auf eine Ansicht anwenden möchte (und sie nicht nur vorübergehend wackeln möchte). Leider führt dies normalerweise dazu, dass die Ansicht vom Bildschirm verschwindet, da das automatische Layout immer noch stattfindet und es jetzt keine Einschränkungen gibt, die uns mitteilen, wo die Ansicht platziert werden soll. Zusätzlich zum Entfernen der Einschränkungen habe ich die Ansicht translatesAutoresizingMaskIntoConstraints
auf JA gesetzt. Die Ansicht funktioniert jetzt auf die alte Art und Weise, ohne dass dies durch Autolayout beeinträchtigt wird. (Es ist offensichtlich von Autolayout betroffen, aber die impliziten Einschränkungen der Autoresize-Maske führen dazu, dass sein Verhalten genau so ist, wie es vor dem Autolayout war.)
Lösung 2: Verwenden Sie nur geeignete Einschränkungen
Wenn dies etwas drastisch erscheint, besteht eine andere Lösung darin, die Einschränkungen so festzulegen, dass sie mit einer beabsichtigten Transformation korrekt funktionieren. Wenn eine Ansicht nur nach ihrer internen festen Breite und Höhe dimensioniert und beispielsweise nur nach ihrer Mitte positioniert wird, funktioniert meine Skalentransformation wie erwartet. In diesem Code entferne ich die vorhandenen Einschränkungen für eine Unteransicht ( otherView
) und ersetze sie durch diese vier Einschränkungen, indem ich ihr eine feste Breite und Höhe gebe und sie nur durch ihre Mitte fixiere . Danach funktioniert meine Skalentransformation:
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]];
Das Ergebnis ist, dass das automatische Layout den Rahmen der Ansicht nicht berührt, wenn Sie keine Einschränkungen haben, die sich auf den Rahmen einer Ansicht auswirken. Dies ist genau das, wonach Sie suchen, wenn eine Transformation beteiligt ist.
Lösung 3: Verwenden Sie eine Unteransicht
Das Problem bei beiden oben genannten Lösungen besteht darin, dass wir die Vorteile von Einschränkungen verlieren, um unsere Sichtweise zu positionieren. Hier ist eine Lösung, die das löst. Beginnen Sie mit einer unsichtbaren Ansicht, deren Aufgabe es ist, ausschließlich als Host zu fungieren, und verwenden Sie Einschränkungen, um sie zu positionieren. Fügen Sie darin die reale Ansicht als Unteransicht ein. Verwenden Sie Einschränkungen, um die Unteransicht in der Hostansicht zu positionieren, beschränken Sie diese Einschränkungen jedoch auf Einschränkungen, die sich beim Anwenden einer Transformation nicht wehren.
Hier ist eine Illustration:
Die weiße Ansicht ist die Hostansicht. Sie sollen so tun, als wäre es transparent und daher unsichtbar. Die rote Ansicht ist die Unteransicht, die durch Anheften der Mitte an die Mitte der Hostansicht positioniert wird. Jetzt können wir die rote Ansicht problemlos skalieren und um ihre Mitte drehen, und die Abbildung zeigt, dass wir dies getan haben:
self.otherView.transform = CGAffineTransformScale(self.otherView.transform, 0.5, 0.5);
self.otherView.transform = CGAffineTransformRotate(self.otherView.transform, M_PI/8.0);
In der Zwischenzeit halten die Einschränkungen für die Hostansicht sie beim Drehen des Geräts an der richtigen Stelle.
Lösung 4: Verwenden Sie stattdessen Layer-Transformationen
Verwenden Sie anstelle von Ansichtstransformationen Ebenentransformationen, die kein Layout auslösen und daher keinen unmittelbaren Konflikt mit Einschränkungen verursachen.
Zum Beispiel kann diese einfache Animation der "Pochen" -Ansicht unter Autolayout unterbrochen werden:
[UIView animateWithDuration:0.3 delay:0
options:UIViewAnimationOptionAutoreverse
animations:^{
v.transform = CGAffineTransformMakeScale(1.1, 1.1);
} completion:^(BOOL finished) {
v.transform = CGAffineTransformIdentity;
}];
Auch wenn sich die Größe der Ansicht am Ende nicht geändert hat, wird lediglich transform
das Layout der Ursachen festgelegt, und Einschränkungen können dazu führen, dass die Ansicht herumspringt. (Fühlt sich das wie ein Fehler an oder was?) Wenn wir jedoch dasselbe mit Core Animation tun (mithilfe einer CABasicAnimation und Anwenden der Animation auf die Ebene der Ansicht), geschieht das Layout nicht und es funktioniert einwandfrei:
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
weiß seine Breite? Klebt es mit der rechten Seite an etwas anderem?