History of Auto Layout constraints

栏目: IT技术 · 发布时间: 4年前

内容简介:Auto Layout was introduced in iOS 6, and it changes the way we think of the layout of our UI elements. It is not the most elegant or beautiful API back then. That's why you might see a lot of Auto Layout library popping up here and there. I also used some

Auto Layout was introduced in iOS 6, and it changes the way we think of the layout of our UI elements. It is not the most elegant or beautiful API back then. That's why you might see a lot of Auto Layout library popping up here and there. I also used some of them at that time, but the APIs around Auto Layout has improved a lot since then. If you are the one who is still using a third-party library for Auto Layout, I want to show how Apple improve this over the years. At the end of this article, you might consider ditch out all those external dependencies and adopt the standard APIs.

Distance Past (iOS 6)

When Auto Layout first came out in iOS 6, there are three ways in which constraints in a user interface layout can be created:

|-[find]-[findNext]-[findField(>=20)]-|

The following is an example of defining constraints of content view with a padding of 20.

override func viewDidLoad() {
    super.viewDidLoad()

    let contentView = UILabel()
    contentView.backgroundColor = .systemPink
    contentView.translatesAutoresizingMaskIntoConstraints = false

    view.addSubview(contentView)

    let views: [String: Any] = [
      "contentView": contentView
    ]

    let verticalConstraints = NSLayoutConstraint.constraints(
        withVisualFormat: "V:|-20-[contentView]-20-|",
        metrics: nil,
        views: views)
    let horizontalConstraints = NSLayoutConstraint.constraints(
        withVisualFormat: "H:|-20-[contentView]-20-|",
        metrics: nil,
        views: views)

    view.addConstraints(horizontalConstraints)
    view.addConstraints(verticalConstraints)
}

Result:

History of Auto Layout constraints

For a simple layout, this might look like a good way to create a layout, but for a complex one, it might not be the case. There is some goodness in VFL, but it's lack of support for a number of layout attributes like height/width percentages and centering, and for me, it can easily go wrong with this ASCII. It is not the best solution, but people still using it because another way of creating a constraint, NSLayout Constraint API, is not that good either.

  1. NSLayoutConstrint APIs. This is the reason where all those libraries gain popularity in the first place. Back then, this is how we define constraints.
let leadingConstraint = NSLayoutConstraint(
    item: contentView,
    attribute: .leading,
    relatedBy: .equal,
    toItem: view,
    attribute: .leading,
    multiplier: 1,
    constant: 20)

let bottomConstraint = NSLayoutConstraint(
    item: contentView,
    attribute: .bottom,
    relatedBy: .equal,
    toItem: view,
    attribute: .bottom,
    multiplier: 1,
    constant: -20)

let topConstraint = NSLayoutConstraint(
    item: contentView,
    attribute: .top,
    relatedBy: .equal,
    toItem: view,
    attribute: .top,
    multiplier: 1,
    constant: 20)

let trailingConstraint = NSLayoutConstraint(
    item: contentView,
    attribute: .trailing,
    relatedBy: .equal,
    toItem: view,
    attribute: .trailing,
    multiplier: 1,
    constant: -20)

view.addConstraints([
    leadingConstraint,
    trailingConstraint,
    topConstraint,
    bottomConstraint
])

The above example would also add a contentView with a padding of 20. Nothing wrong with the API, the concept is solid, it can do everything and support every layout attribute. The only con is its verbosity. It is not a surprise people looking for an alternative, and when they found one, they never returned.

Small Improvement (iOS 8)

In iOS 8, Apple introduces a concept of active state to NSLayoutConstraint. Only active constraints affect the calculated layout. You can enable/disable any constraints without to remove and re-add it to a view.

Activate/Deactivate a collection of constraints

The old way of adding constraints is still compatible with the active state; all constraints pass in addConstraints(_:) will mark as active. Even though we can use addConstraints(_:) , it is no longer a recommended way of doing it. There is a small catch in addConstraints(_:) .

All constraints must involve only views that are within scope of the receiving view. Specifically, any views involved must be either the receiving view itself, or a subview of the receiving view.

This might not be an issue for an experienced developer but might confuse a newcomer on which view should they add constraints to.

You might get this error if you try to add constraints to the wrong ancestor.

"swift Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Impossible to set up layout with view hierarchy unprepared for constraint.' "

To mitigate this, in iOS 8, Apple introduces a new method activate(_:) . The activate(_:) method automatically adds the constraints to the correct views. We don't have to worry about which view should hold a constraint you are about to add.

So, instead of calling addConstraints(_:) on view :

view.addConstraints([
    leadingConstraint,
    trailingConstraint,
    topConstraint,
    bottomConstraint
])

We call NSLayoutConstraint.activate instead:

NSLayoutConstraint.activate([
    leadingConstraint,
    trailingConstraint,
    topConstraint,
    bottomConstraint
])

We also have deactivate(_:) as a new way of removeConstraint(_:) .

view.removeConstraints([
    leadingConstraint,
    trailingConstraint,
    topConstraint,
    bottomConstraint
])

NSLayoutConstraint.deactivate([
    leadingConstraint,
    trailingConstraint,
    topConstraint,
    bottomConstraint
])

Activate/Deactivate individual constraint

You can also activate/deactivate a single constraint by changing isActive property.

let leadingConstraint = NSLayoutConstraint(
    item: contentView,
    attribute: .leading,
    relatedBy: .equal,
    toItem: view,
    attribute: .leading,
    multiplier: 1,
    constant: 20)

leadingConstraint.isActive = true
leadingConstraint.isActive = false

The only time this will break is when you try to activate a constraint whose items have no common ancestor. In short, don't forget to add your view into a view hierarchy before calling activate(_:) , deactivate(_:) , and isActive . Failing to do so, and you will get the following exception.

*** Terminating app due to uncaught exception 'NSGenericException', reason: 'Unable to activate constraint with anchors <NSLayoutXAxisAnchor:0x600002cb44c0 "UIView:0x7f803d407450.leading"> and <NSLayoutXAxisAnchor:0x600002ca4200 "UIView:0x7f803d705d40.leading"> because they have no common ancestor.  Does the constraint or its anchors reference items in different view hierarchies?  That's illegal.'

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x600002443160 UIView:0x7fe0d8404e60.trailing == UIView:0x7fe0d860e910.trailing - 20   (active)>

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKitCore/UIView.h> may also be helpful.

This change solves some pain in adding and removing constraints, but the constraint creation syntax still wordy.

Layout Anchors to the rescue (iOS 9)

Apple finally solves the problem in iOS 9. They come up with a new NSLayoutAnchor . Think of it as a specific point in a view which you can use as a reference point when creating a constraint.

UIView comes with a collection of anchor properties that you can use to create a constraint.

Property Layout Anchor Description
bottomAnchor NSLayoutYAxisAnchor A layout anchor representing the bottom edge of the view's frame.
centerXAnchor NSLayoutXAxisAnchor A layout anchor representing the horizontal center of the view's frame.
centerYAnchor NSLayoutYAxisAnchor A layout anchor representing the vertical center of the view's frame.
firstBaselineAnchor NSLayoutYAxisAnchor A layout anchor representing the baseline for the topmost line of text in the view.
heightAnchor NSLayoutDimension A layout anchor representing the height of the view's frame.
lastBaselineAnchor NSLayoutYAxisAnchor A layout anchor representing the baseline for the bottommost line of text in the view.
leadingAnchor NSLayoutXAxisAnchor A layout anchor representing the leading edge of the view's frame.
leftAnchor NSLayoutXAxisAnchor A layout anchor representing the left edge of the view's frame.
rightAnchor NSLayoutXAxisAnchor A layout anchor representing the right edge of the view's frame.
topAnchor NSLayoutYAxisAnchor A layout anchor representing the top edge of the view's frame.
trailingAnchor NSLayoutXAxisAnchor A layout anchor representing the trailing edge of the view's frame.
widthAnchor NSLayoutDimension A layout anchor representing the width of the view's frame.

Each anchor can only form a constraint with an anchor of the same axis.

Create a constraint with anchor

NSLayoutAnchor comes with a set of methods including operations that you would see in NSLayoutConstraint , e.g., equal to (=), greater than or equal to (>=), less than or equal to(<=). All of these with a variant of parameters, including both multiplier and constant. Even there is a lot of variation, you can access all of them by typing constraint , and Xcode should list all of them for you. To explore all of them, check out Apple Documentation .

History of Auto Layout constraints

Our constraints code will come down to this.

let constraints = [
    contentView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
    contentView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
    contentView.topAnchor.constraint(equalTo: view.topAnchor, constant: 20),
    contentView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -20)
]
NSLayoutConstraint.activate(constraints)

This new API removes a lot of boilerplate code and significantly reduces the line of code.

It took three years before Apple tackle the verbosity problem of Auto Layout; some people seeking an alternative library and never look back. If you are one who is still using external dependency for this job, you might consider using the standard APIs in the next project. With all of these improvements that Apple keeps adding throughout these years, it is more pleasant to work with.


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

B端产品经理必修课

B端产品经理必修课

李宽 / 电子工业出版社 / 2018-9 / 59

《B端产品经理必修课:从业务逻辑到产品构建全攻略》主要讲述了“单个产品管理流程”,以展示B 端产品经理的工作方法及B 端产品的设计方法。《B端产品经理必修课:从业务逻辑到产品构建全攻略》分为三个部分。第一部分主要讲述的是B 端产品经理的工作流程和定义(即单个产品管理流程),以及从事B 端产品经理的职业现状和规划,还包括设计B 端产品时需要了解的指导思想。第二部分是通过各个章节来讲述单个产品管理流程......一起来看看 《B端产品经理必修课》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

随机密码生成器
随机密码生成器

多种字符组合密码

MD5 加密
MD5 加密

MD5 加密工具