如何使用自动布局在纵向模式下垂直堆叠视图并在横向模式下水平堆叠视图?

Posted

技术标签:

【中文标题】如何使用自动布局在纵向模式下垂直堆叠视图并在横向模式下水平堆叠视图?【英文标题】:How to use Auto Layout to stack views vertically in portrait mode and horizontally in landscape mode? 【发布时间】:2014-09-28 22:11:06 【问题描述】:

我有两个视图“视图 A”和“视图 B”。我想在横向模式下并排显示它们,但在纵向模式下一个接一个。如何使用自动布局来实现这一点?

【问题讨论】:

【参考方案1】:

有了新的UIStackView(在 ios 9.0 中引入),现在可以轻松实现。只需在情节提要中添加一个 UIStackView 与您的两个视图并定义所需的比例。

然后在 ViewController 中,您可以直接在方向更改时调整轴的对齐方式:

override func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation) 
    switch UIDevice.currentDevice().orientation 
    case .Portrait:
        self.stackview.axis = .Vertical
    case .PortraitUpsideDown:
        self.stackview.axis = .Vertical
    case .LandscapeLeft:
        self.stackview.axis = .Horizontal
    case .LandscapeRight:
        self.stackview.axis = .Horizontal
    default:
        self.stackview.axis = .Vertical
    

【讨论】:

【参考方案2】:

写在 UIViewController 的 -viewDidLoad 方法中:

[super viewDidLoad];

// Assuming viewA and viewB have already been setup and have their 
// translatesAutoresizingMaskIntoConstraints property set to NO

NSDictionary *views = NSDictionaryOfVariableBindings(viewA,viewB);

NSArray *a1, *a2, *a3, *a4, *a5, *a6, *a7, *a8;

// Construct all of the constraints
a1 = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[viewA]|" options:0 metrics:nil views:views];
a2 = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[viewB]|" options:0 metrics:nil views:views];
a3 = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[viewA][viewB]|" options:0 metrics:nil views:views];
a4 = @[[NSLayoutConstraint constraintWithItem:viewB attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:viewA attribute:NSLayoutAttributeHeight multiplier:1.0 constant:0.0]];
self.portraitConstraints = @[a1,a2,a3,a4];

a5 = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[viewA][viewB]|" options:0 metrics:nil views:views];
a6 = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[viewA]|" options:0 metrics:nil views:views];
a7 = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[viewB]|" options:0 metrics:nil views:views];
a8 = @[[NSLayoutConstraint constraintWithItem:viewB attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:viewA attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0.0]];

self.landscapeConstraints = @[a5,a6,a7,a8];

// Add all of the constraints
for (NSArray *constraints in self.portraitConstraints) 
    [self.view addConstraints:constraints];

for (NSArray *constraints in self.landscapeConstraints) 
    [self.view addConstraints:constraints];


// Activate the constraints based on the orientation
void(^constraintBlock)() = ^
    // I'm using 'active' property instead of adding and removing constraints.  I haven't 
    // checked to see which way is "better" but it works well so I won't bother!
    if (UIDeviceOrientationIsPortrait([[UIApplication sharedApplication]statusBarOrientation])) 
        for (NSArray *constraints in self.landscapeConstraints) 
            for (NSLayoutConstraint *c in constraints) 
                c.active = NO;
            
        
        for (NSArray *constraints in self.portraitConstraints) 
            for (NSLayoutConstraint *c in constraints) 
                c.active = YES;
            
        
     else 
        for (NSArray *constraints in self.portraitConstraints) 
            for (NSLayoutConstraint *c in constraints) 
                c.active = NO;
            
        
        for (NSArray *constraints in self.landscapeConstraints) 
            for (NSLayoutConstraint *c in constraints) 
                c.active = YES;
            
        

    
;

// Call the block immediately to setup the constraints
constraintBlock();

// self.observer is a property I would then use in the viewController's -dealloc 
// method to remove it as an observer of the default NSNotificationCenter
self.observer = [[NSNotificationCenter defaultCenter]addObserverForName:UIApplicationDidChangeStatusBarOrientationNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) 
    // Update the constraints whenever the status bar orientation changes
    constraintBlock();
];

还有很多其他方法可以做到这一点!

【讨论】:

我希望我可以在 IB 中使用尺寸等级来做到这一点。但这仍然有效。谢谢【参考方案3】:

如果您的目标是 iOS 8,则可以利用新的 size classes。虽然这对以前版本的操作系统没有帮助,但它肯定会成为 iOS 8+ 的一项资产。

您可以在 Interface Builder 中通过选择视图控制器并启用大小类来执行此操作:

然后,在构建视图时,在 Storyboard 窗口底部设置要为其创建约束的尺寸类:

您还可以直接从约束的属性检查器视图添加、修改和删除特定于大小类的约束:

【讨论】:

【参考方案4】:

很遗憾,无法设置这样的布局来自动遵循您的要求。在 iOS8 中,借助 Size Classes(如上一个答案中已经提到的),您可以这样做,但仅限于 iPhone,它会在方向更改时更改类。由于您的示例具有 iPad 布局,因此没有这样的东西,您必须自己跟踪方向并相应地更改布局。

编辑: 实际上,只要你只做 iPad 布局,或者你做通用布局,但 iPhone 和 iPad 有相同的布局,而且它只在方向变化时改变,就会有一个 hack 可以按需要运行: 呈现您嵌入在容器视图控制器中的视图控制器,并在容器中覆盖 -viewWillTransitionToSize:withTransitionCoordinator: 并通过调用 -setOverrideTraitCollection:forChildViewController: 来强制更改控制器的特征,其特征与容器的新大小相关(例如:size.width > size .height 在水平方向是UIUserInterfaceSizeClassRegular,在垂直方向是UIUserInterfaceSizeClassCompact

这个过程的问题在于它改变了所有其他使用它的特性的类感知这个视图控制器的方式。

【讨论】:

以上是关于如何使用自动布局在纵向模式下垂直堆叠视图并在横向模式下水平堆叠视图?的主要内容,如果未能解决你的问题,请参考以下文章

iOS Auto Layout - 如何让两个堆叠的纵向视图在横向中并排移动?

在 Objective C 中使用自动布局分离纵向和横向视图

由于纵向尺寸的超视帧,iOS自动布局在横向模式下不正确?

自动布局无法按预期工作

如何针对不同的 iPhone 屏幕尺寸在纵向和横向中使用自动布局

相同的自动布局约束在纵向和横向中表现不同