【问题标题】:Can I use autolayout to provide different constraints for landscape and portrait orientations?我可以使用自动布局为横向和纵向方向提供不同的约束吗?
【发布时间】:2013-07-20 07:45:10
【问题描述】:

是否可以在设备旋转时更改约束?如何实现?

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

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

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

【问题讨论】:

    标签: ios cocoa-touch uiview uiviewcontroller autolayout


    【解决方案1】:

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

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

    话虽如此,如果您真的需要知道方向,UIDevice.currentDevice().orientation 是您的最佳选择。


    原帖:

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

    【讨论】:

    • 您不需要手动“触发”约束更新。 'updateViewConstraints' 方法已经做到了。此评论也是由下面的 rokridi 发表的,但此答案尚未编辑。
    • 不要忘记,当您的视图为背景时,设备可能会旋转。当应用程序处于前台时,您可以使用setNeedsUpdateConstraints
    • 随着 iOS 8 的发布,Size Classes 的概念无论如何都会解决这个问题 ;)
    • @knl,尺寸等级不涉及 iPad 上的纵向和横向(它们都是 wRegular/hRegular)。
    • 至少在 iOS 8 上,方向更改(无论是在活动期间还是在后台)都会触发对 updateViewConstraints 的调用,而无需执行任何操作。
    【解决方案2】:

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

    为了避免故事板的地狱警告,我将所有一组设置为 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);
        }
    }
    

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

    【讨论】:

    • 非常好,谢谢!不幸的是,我也必须这样做,因为我支持 iOS 7,所以还不能充分利用 Size 类。我有一个自定义子视图,并在覆盖的“updateConstraints”中执行与“viewWillLayoutSubviews”相同的操作,然后我需要做的就是从 willAnimateRotationToInterfaceOrientation 调用 setNeedsUpdateConstraints。它确实工作正常。
    • 我还想再给你一个高分来设置优先级以避免警告。
    • 注意:active 属性仅适用于 iOS 8。另外,在注意到这一点的同时,我还在 NSLayoutConstraint.activateConstraints() 和 NSLayoutConstraint.deactivateConstraints() 上找到了一些方便的类方法,您可以在其中传入 IBOutletCollection。在 iOS 7 中,这几乎是相同的技术,但我使用 self.removeConstraint 和 self.addConstraint,这适用于我的情况,但我不知道这对所有情况来说有多可行。
    • 我为所有人保留了优先级 1000,只是在界面生成器中未选中“已安装”
    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      对于正在搜索当前可能的解决方案 (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)
          })
      }
      

      【讨论】:

        【解决方案5】:

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

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

        逆向旋转的过程。

        【讨论】:

        • 如果在应用离开屏幕时旋转设备会发生什么?还有其他我应该注意的边缘情况吗?
        • 您的视图在不可见时不会收到旋转通知。我很确定。事实上,我记得在搜索一些相关解决方案时在一些线程中看到了这一点。我想您需要在 viewWillAppear 上检查设备方向。
        【解决方案6】:

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

        • 横向: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):

        界面会自动重建。

        【讨论】:

        • 男人。约束的优先级似乎允许声明约束一次以使应用程序自动处理它们。但您仍然使用它们来手动检查每个轮换动作。
        • 约束优先级不应声明一次,并且可以在轮换时更改。这种方法允许使用比其他任何方法更少的代码。
        猜你喜欢
        • 1970-01-01
        • 2015-03-22
        • 2023-04-05
        • 1970-01-01
        • 1970-01-01
        • 2017-11-28
        • 1970-01-01
        • 1970-01-01
        • 2013-08-15
        相关资源
        最近更新 更多