48

旋转设备时是否可以更改约束?这怎么可能实现?

一个简单的例子可能是两个图像,在纵向上,一个在另一个之上,但在横向上是并排的。

如果这是不可能的,我还能如何完成这个布局?

我在代码中构建我的视图和约束,而不是使用界面生成器。

4

6 回答 6

40

编辑:使用Xcode 6 中引入的大小类的新概念,您可以轻松地在 Interface Builder 中为特定大小类设置不同的约束。大多数设备(例如所有当前的 iPhone)在横向模式下都有一个紧凑的垂直尺寸类别。

对于一般布局决策来说,这是一个比确定设备方向更好的概念。

话虽这么说,如果你真的需要知道方向,UIDevice.currentDevice().orientation就是要走的路。


原帖:

重写updateViewConstraints方法UIViewController以针对特定情况提供布局约束。这样,布局总是根据情况设置正确的方式。确保它们与情节提要中创建的约束形成一套完整的约束。您可以使用 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];

}
于 2013-11-01T01:33:57.373 回答
17

我正在使用的方法(无论好坏)是在故事板编辑器中定义两组约束(纵向和横向)。

为了避免故事板的地狱警告,我将所有一组设置为优先级 999,这样它就不会到处显示红色。

然后我将所有约束添加到出口集合:

@property (strong, nonatomic) IBOutletCollection(NSLayoutConstraint) NSArray *portraitConstraints;
@property (strong, nonatomic) IBOutletCollection(NSLayoutConstraint) NSArray *landscapeConstraints;

最后,我实现了我ViewControllersviewWillLayout方法:

- (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);
    }
}

这似乎有效。我真的希望你可以在故事板编辑器中设置默认的活动属性。

于 2014-12-29T23:51:31.717 回答
2

我遵循与您相同的方法(没有 nib 文件或情节提要)。您必须更新updateViewConstraints方法中的约束(通过检查设备方向)。无需调用setNeedsUpdateConstraintsupdateViewConstraints因为一旦您更改设备方向,最后一个方法就会自动调用。

于 2013-12-03T10:28:59.977 回答
1

对于正在搜索当前可能的解决方案 (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)
    })
}
于 2020-02-11T19:12:16.960 回答
0

您可以将约束保存为纵向和横向版本的属性或变量,然后在旋转时设置和删除它们。

我已经这样做了,在 xib 中为初始视图制作约束,将它们分配给视图控制器中的插座。在旋转时,我创建备用约束,移除出口但保留它们,插入备用约束。

反转旋转返回的过程。

于 2013-07-21T13:59:42.390 回答
-1

我的想法是通过改变约束优先级来处理方向。
假设优先级是:

  • 景观:910 活跃/10 不活跃。
  • 肖像:920 活跃/20 不活跃。

第 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):

在此处输入图像描述

界面将自动重建。

于 2016-12-09T17:55:16.600 回答