尽管用代码来写 layout constraint 已经比以前要简单许多了,但是还是有一些麻烦的地方。一个简单的例子就是很多第三方布局库都会提供一些这样的写法:

1
2
3
4
foo.snp.makeConstraints { make in
make.center.equalTo(bar)
make.width.equalTo(12)
}

如果用 layout anchor 来写的话,就需要三行了:

1
2
3
4
5
NSLayoutConstraint.activate([
foo.centerXAnchor.constraint(equalTo: bar.centerXAnchor),
foo.centerYAnchor.constraint(equalTo: bar.centerYAnchor),
foo.widthAnchor.constraint(equalToConstant: 12)
])

倒也不是没有办法优化,比如说我们可以给 UIView 增加一个方法直接返回居中相关的两个约束,但最终的写出来却还是很奇怪:

1
2
3
4
NSLayoutConstraint.activate(
[foo.widthAnchor.constraint(equalToConstant: 12)]
+ foo.constraintsToCenter(in: bar)
)

这个时候 function builder 横空出世了。它能让我们将一连串可以转化为一组 NSLayoutConstraint 的表达式串联写在 block 里面,最终 function builder 会将这些表达式合成为一个 [NSLayoutConstraint],我们可以定义一个 protocol LayoutConstraintBuildUp 来代表这些表达式,它们会在 function builder 的 buildBlock 方法中被合并成为一个 [NSLayoutConstraint]

1
2
3
4
5
6
7
8
9
10
protocol LayoutConstraintBuildUp {
func buildUp() -> [LayoutConstraintBuildUp]
}

@_functionBuilder
struct LayoutConstraintBuilder {
static func buildBlock(_ children: LayoutConstraintBuildUp...) -> [LayoutConstraintBuildUp] {
return children.map { $0.buildUp() }
}
}

最终我们就可以为 NSLayoutConstraint 增加一个方法,让其能够接受一个 function builder 作为参数构建约束。

1
2
3
4
5
6
7
8
9
10
11
extension NSLayoutConstraint {
static func activate(@LayoutConstraintBuilder _ constraints: () -> [LayoutConstraintBuildUp]) {
let all = constraints().flatMap { $0.buildUp() }
activate(all)
}
}

NSLayoutConstraint.activate {
foo.widthAnchor.constraint(equalToConstant: 12)
foo.constraintsToCenter(in: bar)
}

或许你觉得这样写还不够简单,那不妨考虑一下用自定义的运算符来代替单个约束的创建,比如说这样 foo.widthAnchor => 12。这样一来像是 SnapKit 这样的第三方布局库就不见得有什么优势了,能够用原生的话,当然还是原生的好。

然而 function builder 甚至还没进入 review 阶段。