NSLayoutConstraint:Apple自动布局的发展史

Auto Layout

iOS的自动布局,多少有点陌生了,平时也都是用三方库比较多,今天就看一下Apple在布局上的一些关键API,看能不能达到完全使用原生API完成这个工作。

iOS6时代(VFL)

Auto Layout 是Apple在iOS6引进的一个技术,也让我们在frame之外有了一个对UI布局多了个新的视角。虽然它刚出来的时候也不好用,甚至没啥人在用,这也是有这么多Auto Layout三方库出现的原因。回到过去,看一下它的具体使用

swift 复制代码
let contentView = UILabel()
contentView.backgroundColor = .orange
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(verticalConstraints)
view.addConstraints(horizontalConstraints)

与我现在对Layout的认知相比,比较跳脱的点有这几个

  • 需要一个数组记录有哪些subviewlet views: [String: Any] = [ "contentView": contentView ]
  • 布局代码的语法,也就是所谓的VFL,写法真的是......
  • 定义的约束verticalConstraints和horizontalConstraints是添加到整个superView上的,可能是在创建的时候传入了所有subview的数组,内部有个映射关系

NSLayoutConstraint APIs

应该是Apple也看到了VFL没有推行下去,在NSLayoutConstraint上提供了一套更友好的API来创建约束

swift 复制代码
let leadingConstraint = NSLayoutConstraint(item: contentView, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: 40)
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
])

这个API和写法,能处理所有的布局约束,唯一难受的就是写起来比较繁琐,虽然API定义很清楚,但这样的重复再重复也是很容易出错的。

iOS8时代(active state)

在iOS8中,Apple对NSLayoutConstraint添加了active state的属性,只有active的constraint才会在最终的Layout计算中生效,可以enable或者disable一个constraint,而不用像之前要remove或者re-add一个constraint。

NSLayoutConstraint()创建出来的constraint对象默认active是false的,你可以单独设置它的active state,比如leadingConstraint.isActive = true 。当然如果它被addConstraints里了,也会被置为active。

但是整体上的写法还是没有太多改进,还是用addConstraints添加所有的约束对象,如果需要布局的元素多起来,所有的布局对象都添加到view身上,我甚至不确定是在给哪个subview或者该怎么给指定的subview添加约束。

你收到过 Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Impossible to set up layout with view hierarchy unprepared for constraint.'这种错误吗,头不头疼,基本就是因为给错误的父对象添加约束。为了缓解这个问题,iOS8中,Apple给出了active(_:) api。

active(_:)

会自动将约束添加到正确的view上,马不停蹄的删了所有的addConstraints,取而代之是的NSLayoutConstraint.activate

swift 复制代码
NSLayoutConstraint.activate([
    leadingConstraint,
    trailingConstraint,
    topConstraint,
    bottomConstraint
])

相对应的,可以使用deactivate(_:) 取代之前的removeConstraint(_:) (这种看起来就不是很聪明的API)。使用active API,基本在Layout的事情上就很少有crash的问题了,除非是你使用之前,没有将constraint对象添加到view上,那你可能会收到类似 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.' 的crash信息

iOS9时代 Layout Anchors

swift 复制代码
NSLayoutConstraint.activate([
    contentView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
    contentView.topAnchor.constraint(equalTo: view.topAnchor, constant: 20),
    contentView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
    contentView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -20)
])

这个版本的约束写法基本跟我认知中的Layout该有的样子比较像了

  • 使用NSLayoutConstraint.active,直接把约束对象添加到指定的subview上,而不是addConstraints添加到super View上,这避免了给错误的super view添加约束造成的crash
  • Anchors的使用,可以理解为是subview(要添加约束的对象)上的特定点,创建约束的时候将其作为参考点,创建constraint的写法也变得简单高效。

NSLayoutAnchor创建constraint时,有一整套的面向对象的API可用

相对于使用NSLayoutConstraint会更加直观,比如将view的leading与另一个view的leading对齐

NSLayoutAnchor: view1.leadingAnchor.constraint(equalTo: view2.leadingAnchor),

NSLayoutConstraint: NSLayoutConstraint(item: view1, attribute: .leading, relatedBy: .equal, toItem: view2, attribute: .leading, multiplier: 1.0, constant: 0.0)

到这一步其实已经很接近Snapkit的写法了,所以在项目中,你会直接使用这些Apple自带的Layout API吗

相关推荐
alwaysrun1 小时前
Web之wasm-bindgen详解
前端·wasm·bindgen
鬼手点金1 小时前
如何选择模型
java·前端·人工智能·git·深度学习
恋猫de小郭1 小时前
Dart 3.15 新特性:this 终于支持类型提升了
android·前端·flutter
IT_陈寒2 小时前
Vue开发实战:从入门到精通
前端·人工智能·后端
ii_best2 小时前
ios手机开发安卓脚本开发软件[按键精灵教程]从「找最小值」到「Boss 最近刷新」再到「屏幕范围极值」
android·ios·智能手机·自动化·按键精灵
znnnk2 小时前
2026 前端工程化新纪元:Rust 重塑工具链,AI Agent 接管流水线
前端·人工智能·rust
王大师王文峰2 小时前
HTML 入门到进阶:语义化标签、表单与多媒体一篇打通【语义化布局、表单校验、video/audio、Canvas、无障碍与 SEO】
前端·css·node.js·html·css3·jquery·html5
风骏时光牛马2 小时前
AI_Coding:智能代码创作助手
前端
东坡肘子2 小时前
谁是 AI-relay App 的真正威胁 -- 肘子的 Swift 周报 #158
人工智能·swiftui·swift
剪刀石头布啊11 小时前
vue-react为何要虚拟dom
前端