【问题标题】:How can I rearrange views when autorotating with autolayout?使用自动布局自动旋转时如何重新排列视图?
【发布时间】:2015-08-11 11:02:34
【问题描述】:

我经常遇到这个问题,我希望我的视图以一种方式排列纵向,而以一种完全不同的方式排列横向。

对于一个简化的示例,请考虑以下内容:

纵向:

风景:

注意纵向图像是如何垂直堆叠的,而横向图像是并排的?

当然,我可以手动计算位置和大小,但是对于更复杂的视图,这很快就会变得复杂。另外,我更喜欢使用自动布局,这样设备特定的代码就更少了(如果有的话)。但这似乎需要编写 很多 的自动布局代码。有没有办法通过情节提要为这类东西设置约束?

【问题讨论】:

标签: ios autolayout uikit nslayoutconstraint xcode-storyboard


【解决方案1】:

现在 UIStackView 在 iOS9 中可用,更简单且有保证的解决方案是将两个视图都添加到 stackview,将 stackview 链接到 IBOutlet,然后将以下代码添加到 viewcontroller:

- (void) adjustViewsForOrientation:(UIInterfaceOrientation) orientation {

if(orientation == UIInterfaceOrientationPortrait || orientation == UIInterfaceOrientationPortraitUpsideDown || orientation == UIInterfaceOrientationUnknown)
    self.stackView.axis = UILayoutConstraintAxisVertical;
else
    self.stackView.axis = UILayoutConstraintAxisHorizontal;

[self.stackView setNeedsLayout];
[self.stackView setNeedsDisplay];

}

并从

调用此方法
-(void) willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration

【讨论】:

    【解决方案2】:

    Xcode 的界面构建器有两个未被充分利用的特性,我们可以在这里利用这些特性来使这类事情完全同步。

    1. 您可以为NSLayoutConstraints 创建IBOutlet 连接。
    2. 您可以连接“出口集合”,它是IBOutlet 对象的数组。

    因此,考虑到这两件事,我们要做的主要是为一个方向创建所有自动布局约束,将它们全部连接到一个插座集合中。现在,对于这些约束中的每一个,取消选中界面生成器上的“已安装”选项。然后为另一个布局制作我们所有的奥特莱斯系列,并将它们连接到另一个奥特莱斯系列。我们可以根据需要创建任意数量的布局组。

    需要注意的是,我们需要一个对直接安装了约束的 UI 元素的引用,并且我们需要一个单独的插座集合,不仅针对我们想要的每个布局,还针对每个安装了约束的 UI 对象直接上。

    让我们看一下您问题中相当简化的示例。

    如果您查看左侧的约束列表,您会看到其中一半是灰色的。灰色的约束是景观约束。我创建了所有这些,然后为它们中的每一个取消选中“已安装”:

    同时,未变灰的、正常外观的约束是纵向约束。对于这些,我将它们“安装”。完全没有必要安装其中的任何一个,但如果您安装了多个集合,您会遇到问题(它们很可能会发生冲突)。

    请务必不要为其中任何一项选中“在构建时删除”。我们不希望在构建时“移除”约束。这仅仅意味着根本没有创建约束(因此我们将失去对它的引用)。如果我们在约束条件中有IBOutlet 时保留此检查标记,Xcode 将生成警告:

    不支持的配置
    连接到占位符约束。在 IB 中标记为占位符的约束不应有任何连接,因为这些约束未编译到文档中,并且在运行时不存在。

    无论如何,现在我们需要将约束连接到一个插座,以便我们可以在运行时访问它们。

    按住 Ctrl 并单击并从其中一个约束拖动到源代码文件,就像连接任何其他 UI 元素一样。在弹出的对话框中,选择 Outlet Collection 和一个描述性名称:

    现在将与该约束组匹配的所有其他约束连接到同一个出口集合中:

    一旦我们完成了所有约束的连接,只需在适当的时间移除/添加它们即可。

    对于问题中描述的场景这样一个简单的例子,我们可以用这样的代码覆盖updateViewConstraints

    斯威夫特

    class ViewController: UIViewController {
        @IBOutlet var landscapeConstraints: [NSLayoutConstraint]!
        @IBOutlet var portraitConstraints: [NSLayoutConstraint]!
    
        override func updateViewConstraints() {
            let isPortrait = self.view.bounds.width < self.view.bounds.height
    
            self.view.removeConstraints(self.portraitConstraints)
            self.view.removeConstraints(self.landscapeConstraints)
            self.view.addConstraints(isPortrait ? self.portraitConstraints : self.landscapeConstraints)
    
            super.updateViewConstraints()
        }
    }
    

    目标-C

    @interface ViewController()
    
    @property (strong, nonatomic) IBOutletCollection(NSLayoutConstraint) NSArray *landscapeConstraints;
    @property (strong, nonatomic) IBOutletCollection(NSLayoutConstraint) NSArray *portraitConstraints;
    
    @end
    
    @implementation ViewController
    
    - (void)updateViewConstraints {
        BOOL isPortrait = self.view.bounds.size.width < self.view.boudns.size.height;
    
        [self.view removeConstraints:self.portraitConstraints];
        [self.view removeConstraints:self.landscapeConstraints];
        [self.view addConstraints:(isPortrait ? self.portraitConstraints : self.landscapeConstraints)];
    
        [super updateViewConstraints];
    }
    
    @end
    

    我们不会检查视图之前的约束集,因此只需删除我们的两个约束集,然后添加我们想要使用的适当集。

    这是我们在运行时完全改变对象上的一整套约束所需的全部代码。这允许在界面构建器中设置我们所有的约束,而不必以编程方式进行(我发现这有点乏味且容易出错)。

    最终结果?非常漂亮的自动旋转重新排列,无需费力就可以完美完成自动布局代码:

    【讨论】:

    • 这是 Stack Overflow 答案的缩影:一个解释清楚、优雅的解决方案,可以解决烦人、广泛适用、实用但复杂的任务。你刚刚为我节省了几个小时的时间,我相信在接下来的几个月里,其他人也会如此。为真正规范的问题/答案对喝彩!
    猜你喜欢
    • 2015-04-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-17
    • 2013-07-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多