[编辑:警告:整个随后的讨论可能会被iOS 8过时,或者至少在很大程度上得到缓解,iOS 8可能不再会导致在应用视图转换时触发布局的错误。
自动布局与视图转换
自动布局在视图转换中不能很好地发挥作用。据我所知,原因是您不应该弄乱具有变换(默认身份变换除外)的视图的框架-但这正是自动布局的作用。自动布局的工作方式是在layoutSubviews
运行时冲破所有约束并相应地设置所有视图的框架。
换句话说,约束不是魔术。他们只是一个待办事项清单。layoutSubviews
是待办事项清单完成的地方。它通过设置框架来实现。
我不禁将此视为错误。如果我将此转换应用于视图:
v.transform = CGAffineTransformMakeScale(0.5,0.5);
我希望看到该视图的中心在与以前相同的位置,并且只有其一半大小。但是根据它的约束,可能根本不是我所看到的。
[实际上,这里还有第二个惊喜:将变换应用于视图会立即触发布局。在我看来,这是另一个错误。也许这是第一个错误的核心。我期望的是至少在布局时间之前能够避免变形,例如旋转设备-就像我可以在布局时间之前避免使用帧动画一样。但是实际上布局时间是即时的,这似乎是错误的。]
解决方案1:无约束
当前的一种解决方案是,如果我要对视图应用半永久性转换(而不仅仅是以某种方式临时摆动),以消除影响它的所有约束。不幸的是,这通常会导致视图从屏幕上消失,因为仍然会进行自动布局,并且现在没有约束告诉我们将视图放置在何处。因此,除了消除约束之外,我还将视图设置translatesAutoresizingMaskIntoConstraints
为“是”。现在,该视图以旧的方式工作,实际上不受自动布局的影响。(这是由自动布局的影响,很明显,但隐含的自动尺寸面具限制导致其行为是就像它是自动布局之前。)
解决方案2:仅使用适当的约束
如果这看起来有些过激,则另一种解决方案是将约束设置为与预期的转换一起正常工作。例如,如果一个视图仅由其内部固定宽度和高度确定大小,并仅由其中心定位,则我的缩放变换将按预期工作。在这段代码中,我删除了子视图(otherView
)上的现有约束,并用这四个约束替换了它们,为其提供了固定的宽度和高度,并仅靠其中心固定。在那之后,我的比例转换工作了:
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]];
结果是,如果您没有影响视图框架的约束,则自动布局不会碰到视图框架-这就是您进行变换后所要执行的操作。
解决方案3:使用子视图
上述两种解决方案的问题在于,我们失去了约束来定位我们的观点的好处。因此,这里有一个解决方案。从一个不可见的视图开始,其工作仅是充当宿主,然后使用约束对其进行定位。在其中,将真实视图作为子视图。使用约束将子视图放置在宿主视图中,但将这些约束限制为在应用转换时不会反击的约束。
这是一个例子:
白色视图是主机视图;您应该假装它是透明的,因此是不可见的。红色视图是其子视图,通过将其中心固定到宿主视图的中心来定位。现在,我们可以毫无问题地缩放和旋转红色视图,并且该插图确实表明我们已经这样做了:
self.otherView.transform = CGAffineTransformScale(self.otherView.transform, 0.5, 0.5);
self.otherView.transform = CGAffineTransformRotate(self.otherView.transform, M_PI/8.0);
同时,当我们旋转设备时,对宿主视图的约束将其保持在正确的位置。
解决方案4:改为使用层变换
代替视图变换,请使用图层变换,该变换不会触发布局,因此不会引起与约束的立即冲突。
例如,这个简单的“跳动”视图动画很可能会在自动布局下中断:
[UIView animateWithDuration:0.3 delay:0
options:UIViewAnimationOptionAutoreverse
animations:^{
v.transform = CGAffineTransformMakeScale(1.1, 1.1);
} completion:^(BOOL finished) {
v.transform = CGAffineTransformIdentity;
}];
即使最后视图的大小没有变化,仅设置其transform
原因布局就可以发生,而约束条件可以使视图跳动。(这听起来像是个错误还是什么?)但是,如果我们对Core Animation做同样的事情(使用CABasicAnimation并将动画应用于视图的图层),则不会发生布局,并且可以正常工作:
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
知道它的宽度?它是否将其右边粘在其他东西上?