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.


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

查看所有标签

猜你喜欢:

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

第三次浪潮

第三次浪潮

托夫勒 / 黄明坚 / 中信出版社 / 2006-6 / 38.00元

《第三次浪潮》作者托夫勒在20多年前预见的未来是:跨国企业将盛行;电脑发明使SOHO(在家工作)成为可能;人们将摆脱朝九晚五工作的桎梏;核心家庭的瓦解;DIY(自己动手做)运动的兴起……时过境迁,如今我们才发现托夫勒的预言竟大多已成为了现实。   20年前的《第三次浪潮》在打开国门之初给人们心灵造成的冲击,其影响至今仍然连绵不绝。托夫勒在这本书中将人类社会划分为三个阶段:第一次浪潮为农业阶段......一起来看看 《第三次浪潮》 这本书的介绍吧!

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

在线压缩/解压 HTML 代码

SHA 加密
SHA 加密

SHA 加密工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具