iOS Autolayout 自适应 UI 问题

标签 ios swift autolayout adaptive-layout

我想根据宽度是否大于高度来更改布局。但我不断收到布局警告。我看了两个关于自适应布局的 WWDC 视频,这很有帮助,但没有解决问题。我使用以下代码创建了一个简单版本的布局。这只是为了让人们可以重现我的问题。代码在 iPhone 7 plus 上运行。

import UIKit

class ViewController: UIViewController {

    var view1: UIView!
    var view2: UIView!

    var constraints = [NSLayoutConstraint]()

    override func viewDidLoad() {
        super.viewDidLoad()

        view1 = UIView()
        view1.translatesAutoresizingMaskIntoConstraints = false
        view1.backgroundColor = UIColor.red

        view2 = UIView()
        view2.translatesAutoresizingMaskIntoConstraints = false
        view2.backgroundColor = UIColor.green

        view.addSubview(view1)
        view.addSubview(view2)

        layoutPortrait()
    }

    func layoutPortrait() {
        let views = [
            "a1": view1,
            "a2": view2
        ]

        constraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|[a1]|", options: [], metrics: nil, views: views)
        constraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|[a2]|", options: [], metrics: nil, views: views)
        constraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|[a1(450)][a2]|", options: [], metrics: nil, views: views)
        NSLayoutConstraint.activate(constraints)
    }

    func layoutLandscape() {
        let views = [
            "a1": view1,
            "a2": view2
        ]

        constraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|[a1(a2)][a2(a1)]|", options: [], metrics: nil, views: views)
        constraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|[a1]|", options: [], metrics: nil, views: views)
        constraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|[a2]|", options: [], metrics: nil, views: views)
    NSLayoutConstraint.activate(constraints)
    }

    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
         super.viewWillTransition(to: size, with: coordinator)

         NSLayoutConstraint.deactivate(constraints)

         constraints.removeAll()

         if (size.width > size.height) {
             layoutLandscape()
         } else {
            layoutPortrait()
         }

     } 

}

当我旋转几次时,xcode 会记录警告。我想我把布局切换到早期,因为它仍在变化,但我已经将纵向高度设置为大或其他。有人知道我做错了什么吗?

约束警告:(从横向返回纵向时发生)

[LayoutConstraints] Unable to simultaneously satisfy constraints.
    Probably at least one of the constraints in the following list is one you don't want. 
    Try this: 
        (1) look at each constraint and try to figure out which you don't expect; 
        (2) find the code that added the unwanted constraint or constraints and fix it. 
(
    "<NSLayoutConstraint:0x618000081900 V:|-(0)-[UIView:0x7ff68ee0ac40]   (active, names: '|':UIView:0x7ff68ec03f50 )>",
    "<NSLayoutConstraint:0x618000081d60 UIView:0x7ff68ee0ac40.height == 450   (active)>",
    "<NSLayoutConstraint:0x618000083cf0 V:[UIView:0x7ff68ee0ac40]-(0)-[UIView:0x7ff68ee0ade0]   (active)>",
    "<NSLayoutConstraint:0x618000083d40 V:[UIView:0x7ff68ee0ade0]-(0)-|   (active, names: '|':UIView:0x7ff68ec03f50 )>",
    "<NSLayoutConstraint:0x600000085d70 'UIView-Encapsulated-Layout-Height' UIView:0x7ff68ec03f50.height == 414   (active)>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x618000081d60 UIView:0x7ff68ee0ac40.height == 450   (active)>

最佳答案

你是对的,你做约束工作太早了。 <NSLayoutConstraint:0x600000085d70 'UIView-Encapsulated-Layout-Height' UIView:0x7ff68ec03f50.height == 414 ... 是系统为 View Controller 的横向 View 高度创建的约束 - 为什么它是 414,正好是 iPhone 5.5(6/7 Plus)横向高度。旋转尚未开始,垂直约束与 450 高度约束冲突,您收到警告。

因此您必须在旋转完成后添加约束。 viewWillLayoutSubviews是合乎逻辑的地方,因为当 View 大小准备好但在屏幕上出现任何内容之前会调用它,并且可以保存系统它本来可以进行的布局传递。

但是,要消除警告,您仍然需要删除 viewWillTransition(to size: with coordinator:) 中的约束.系统在 viewWillLayoutSubviews() 之前进行新的自动布局计算被调用,当从肖像转到风景时,您会以另一种方式收到警告。

编辑:正如@sulthan 在评论中指出的那样,由于其他事情可以触发布局,因此您还应该始终在添加约束之前删除它们。如果[constraints]数组被清空没有效果。由于停用后清空数组是一个关键步骤,它应该在自己的方法中,所以 clearConstraints() .

此外,请记住检查初始布局中的方向 - 您调用 layoutPortrait()viewDidLoad()想当然这不是问题。

新/更改方法:

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()

    // Should be inside layoutPortrait/Landscape methods, here to keep code sample short.
    clearConstraints() 

    if (self.view.frame.size.width > self.view.frame.size.height) {
        layoutLandscape()
    } else {
        layoutPortrait()
    }
}

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)

    clearConstraints()
}

/// Ensure array is emptied when constraints are deactivated. Can be called repeatedly. 
func clearConstraints() {
    NSLayoutConstraint.deactivate(constraints) 
    constraints.removeAll()
}

关于iOS Autolayout 自适应 UI 问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41656944/

相关文章:

ios - 所有章节标题显示在一个章节中

ios - -(void)updateConstraints 中的动画约束变化

ios - 如何在 UITableViewCell 内实现动态大小的 UICollectionView?

ios - Firebase ios : get list of users by IDs

Swift - 在解码可选的 Codable 值时,有没有办法区分不存在的字段或为 nil/null 的字段

ios - 获取文本字段上的 "bottom space to superview"约束

arrays - 字典,其中值包含字典,值包含数组

ios - CGImageCreateWithMaskingColors 不适用于 iOS7

ios - RTL(从右到左)通过自动布局在 iOS 6.1/iOS 7.0 上损坏?

iphone - MKMapView 添加特定位置