当使用自动布局进行自动旋转时,我该如何重新排列视图?

31

我经常遇到这样的问题,想要以一种方式在竖屏下排列我的视图,在横屏下又以非常不同的方式排列。

为了举个简单的例子,请考虑以下内容:

竖屏:

横屏:

注意,竖屏下图片是垂直排列的,而在横屏下它们是并排的。

当然,我可以手动计算位置和尺寸,但对于更复杂的视图,这很快就变得复杂。此外,我更希望使用自动布局,这样就可以减少设备特定的代码(如果有的话)。但似乎要编写大量自动布局代码。是否有一种方法可以通过故事板设置这种约束?


这个问题值得就尺寸类别给出一个答案…… - nhgrif
我找到的解决这个确切问题最好、最简单的教程来自raywenderlich http://www.raywenderlich.com/83276/beginning-adaptive-layout-tutorial 注意:似乎只能在iOS 8以上版本中使用 - Bruce
2个回答

35

Xcode的界面生成器有两个不常用的功能,我们可以利用这些来实现这种同步。一是可以为NSLayoutConstraints创建IBOutlet连接;二是可以连接“outlet collection”,即IBOutlet对象的数组。

因此,我们要做的就是为一个方向创建所有的自动布局约束,并将它们全部连接到一个outlet集合中。然后,对于每个约束条件,取消界面生成器上的“Installed”选项。接着,为另一个布局创建所有的outlet集合,并将它们连接到另一个outlet集合中。我们可以创建任意多个这样的布局组。

需要注意的是,我们将需要引用任何直接安装了约束的UI元素,并且不仅需要为每个所需的布局创建一个outlet集合,而且还需要为每个直接安装了约束的UI对象创建一个单独的outlet集合。

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

如果您在左侧的约束列表中查看,可以看到它们中的一半是灰色的。灰色的约束是横向约束。我先创建了所有这些约束,然后为每个约束取消选中“Installed”:

enter image description here

与此同时,未变灰、看起来正常的约束是纵向约束。对于这些约束,我保留了“Installed”。完全没有必要保留任何一个约束,但是如果保留多个已安装的约束集合(它们可能会冲突),您将遇到问题。

不要勾选任何一个约束的“Remove at build time”选项,我们不希望在编译时“删除”约束。这只是意味着根本不创建该约束(因此我们将失去对它的引用)。如果我们在有约束的IBOutlet的情况下保留此选项,Xcode将生成警告:

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.

无论如何,现在我们需要把约束连接到outlet,以便在运行时访问它们。

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

enter image description here

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

enter image description here

完成所有约束的连接后,只需要在适当的时间删除/添加它们。

对于像问题描述中的这种简单示例,我们可以通过重写updateViewConstraints并使用以下代码来实现:

Swift

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

Objective-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

我们不检查视图先前使用了哪个约束集,所以只需删除我们的两个约束集,然后添加我们想要使用的适当的约束集。

这是我们需要的所有代码,可以完全在运行时更改对象上的整个约束集。这允许我们在接口构建器中设置所有约束,而不必通过编程方式执行(我觉得有点乏味和容易出错)。

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


10
这是一篇典型的Stack Overflow答案:对于一个烦人、广泛适用但又复杂的任务,提供了一个优雅并经过良好解释的解决方案。你刚刚节省了我数小时的时间,相信在未来几个月里也会对其他人有同样的帮助。非常感谢你提供了这样一对极具参考价值的问题/答案! - jscs

1
现在,由于 iOS9 中提供了 UIStackView,一个更简单和可靠的解决方案是将两个视图添加到 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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接