【发布时间】:2013-07-20 07:45:10
【问题描述】:
是否可以在设备旋转时更改约束?如何实现?
一个简单的例子可能是两张图片,在纵向时,它们一个在另一个之上,但在横向时是并排的。
如果这是不可能的,我还能如何完成这个布局?
我正在代码中构建我的视图和约束,而不是使用界面生成器。
【问题讨论】:
标签: ios cocoa-touch uiview uiviewcontroller autolayout
是否可以在设备旋转时更改约束?如何实现?
一个简单的例子可能是两张图片,在纵向时,它们一个在另一个之上,但在横向时是并排的。
如果这是不可能的,我还能如何完成这个布局?
我正在代码中构建我的视图和约束,而不是使用界面生成器。
【问题讨论】:
标签: ios cocoa-touch uiview uiviewcontroller autolayout
编辑:使用 Xcode 6 中引入的 Size Classes 新概念,您可以轻松地在 Interface Builder 中为特定大小类设置不同的约束。大多数设备(例如所有当前的 iPhone)在横向模式下都有 Compact 垂直尺寸类别。
对于一般布局决策而言,这是一个比确定设备方向更好的概念。
话虽如此,如果您真的需要知道方向,UIDevice.currentDevice().orientation 是您的最佳选择。
原帖:
覆盖UIViewController 的updateViewConstraints 方法,为特定情况提供布局约束。这样,布局总是根据情况设置正确的方式。确保它们与情节提要中创建的约束形成一套完整的约束。您可以使用 IB 设置您的一般约束,并将那些需要更改的主题标记为在运行时删除。
我使用以下实现来为每个方向呈现一组不同的约束:
-(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];
}
现在,您需要做的就是在情况发生变化时触发约束更新。覆盖 willAnimateRotationToInterfaceOrientation:duration: 以在方向更改时为约束更新设置动画:
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
[super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
[self.view setNeedsUpdateConstraints];
}
【讨论】:
setNeedsUpdateConstraints。
updateViewConstraints 的调用,而无需执行任何操作。
我使用的方法(无论好坏)是在故事板编辑器中定义两组约束(纵向和横向)。
为了避免故事板的地狱警告,我将所有一组设置为 999 的优先级,这样它就不会到处显示红色。
然后我将所有约束添加到出口集合:
@property (strong, nonatomic) IBOutletCollection(NSLayoutConstraint) NSArray *portraitConstraints;
@property (strong, nonatomic) IBOutletCollection(NSLayoutConstraint) NSArray *landscapeConstraints;
最后,我实现了我的ViewControllers 的viewWillLayout 方法:
- (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);
}
}
这似乎有效。我真的希望您可以在故事板编辑器中设置默认的活动属性。
【讨论】:
我采用与您相同的方法(没有 nib 文件或情节提要)。您必须在 updateViewConstraints 方法中更新您的约束(通过检查设备方向)。无需在updateViewConstraints 中调用setNeedsUpdateConstraints,因为一旦您更改设备方向,最后一个方法就会自动调用。
【讨论】:
对于正在搜索当前可能的解决方案 (Swift) 的任何人,请在此 UIViewController 函数中更新您的约束:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {}
每次旋转设备时都会调用它。它允许您响应这些更改,甚至为它们设置动画。 为了给您更好的概览,这就是我在上一个项目中通过更改两个子视图的约束来更改布局的方式。在纵向模式下,我的子视图相互叠加,在横向模式下,子视图并排。
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
// 1. I recommend to remove existing constraints BEFORE animation, otherwise Xcode can yell at you "Unable to simultaneously satisfy constraints"
removeConstraintsOfSubview()
coordinator.animate(alongsideTransition: { [unowned self] context in
// 2. By comparing the size, you can know whether you should use portrait or landscape layout and according to that remake or add specific constraints
if size.height > size.width {
self.setPortraitLayout()
} else {
self.setLandscapeLayout()
}
// 3. If you want the change to be smoothly animated call this block here
UIView.animate(withDuration: context.transitionDuration) {
self.view.layoutIfNeeded()
}
}, completion: { _ in
// After the device is rotated and new constraints are added, you can perform some last touch here (probably some extra animation)
})
}
【讨论】:
您可以将约束保存为纵向和横向版本的属性或变量,然后在旋转时设置和删除它们。
我已经这样做了,在 xib 中为初始视图创建约束,将它们分配给视图控制器中的插座。在旋转时,我创建备用约束,移除出口但保留它们,插入备用。
逆向旋转的过程。
【讨论】:
我的想法是通过更改约束优先级来处理方向。
假设优先级是:
第 1 步: 在 Storyboard 中创建带有约束的横向(或纵向)设计。
第 2 步:对于约束,该约束必须仅对横向模式集有效优先级为 10。
第 3 步:为纵向模式添加约束并将其优先级设置为 920。
在willAnimateRotationToInterfaceOrientation添加代码:
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;
}
}
这种方法的优势 - 在 Interface Builder 中轻松调整。当我们需要切换到任意方向时,我们按优先级选择所有约束并同时改变它们(910->10,20->920):
界面会自动重建。
【讨论】: