且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

使用自动布局自动旋转时如何重新排列视图?

更新时间:2023-12-05 20:58:04

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

There are two underused features of Xcode's interface builder we can make use of here to make exactly this sort of thing a synch.

  1. 您可以为 NSLayoutConstraints 创建 IBOutlet 连接.
  2. 您可以连接出口集合",这是一个 IBOutlet 对象数组.
  1. You can create IBOutlet connections for NSLayoutConstraints.
  2. You can hook up "outlet collections", which is an array of IBOutlet objects.

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

So with these two things in mind, the gist of what we want to do is create all of our autolayout constraints for one orientation, hook them all up in an outlet collection. Now, for each of these constraints, uncheck the "Installed" option on interface builder. Then make all of our outlet collections for another layout, and hook them up to another outlet collection. We can create as many of these layout groups as we want.

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

It's important to note that we will need a reference to any UI element which has constraints installed on it directly, and we will need a seperate outlet collection not just for each layout we want, but for each UI object which has constraints installed on it directly.

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

Let's take a look at the fairly simplified example from your question.

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

If you look in the constraints list on the left, you can see half of them are grayed-out. The grayed-out constraints are the landscape constraints. I created all of these, then unchecked "Installed" for each of them:

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

Meanwhile, the ungrayed, normal looking constraints are the portrait constraints. For these, I left them "Installed". It's completely unnecessary to leave any of them installed, but you will run into problems if you leave multiple sets installed (they most likely conflict).

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

Be sure not to check "Remove at build time" for any of these. We don't want the constraints "removed" at build time. This simply means the constraint isn't created at all (so we'll lose the reference to it). If we leave this check marked while we have an IBOutlet to the constraint, Xcode will generate a warning:

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

Unsupported Configuration
Connection to placeholder constraint. Constraints marked as placeholders in IB should not have any connections since these constraints are not compiled into the document and will not exist at runtime.

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

Anyway, so now we need to hook up the constraints to an outlet so we can access them at run time.

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

Hold Ctrl and click and drag from one of the constraints to your source code file, just as you would connect any other UI element. On the dialog that pops up, choose Outlet Collection and a descriptive name:

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

Now hook up all of the other constraints that match that constraint group into the same outlet collection:

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

Once we've finished hooking up all of our constraints, it's just a matter of removing/adding them at the appropriate time.

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

For such a simple example as the scenario described in the question, we can override updateViewConstraints with some code like this:

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

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

We're not checking which set of constraints the view previously had, so just remove both of our constraint sets, and then add the appropriate set we want to use.

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

This is all the code we need to manage completely changing out an entire set of constraints on an object at run time. This allows to work in the interface builder to set all of our constraints up instead of having to do it programmatically (which I find a little more tedious and error-prone).

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

The end result? Very nice looking autorotation rearrangement without pulling your hair out getting the autolayout code done perfectly: