ConstraintsHolder 是一个基于现代 Anchors API 的轻量级 Auto Layout 框架,它通过抽象化约束的存储来简化约束的使用。 当你需要频繁更改约束时,例如在具有动态 UI 的应用程序中,它尤其有用。
要通过 Swift Package Manager (SPM) 安装,只需执行以下操作
考虑以下常见示例
private let vw = UIView()
private var vwTopConstraint: NSLayoutConstraint?
...
override func viewDidLoad() {
super.viewDidLoad()
vw.translatesAutoResizingMaskIntoConstraints = false
view.addSubview(vw)
let topConstraint = vw.topAnchor.constraint(equalTo: view.topAnchor, constant: 20)
vwTopConstraint = topConstraint
NSLayoutConstraint.ativate([
topConstraint,
// other constraints
])
}
...
func changeConstraint() {
vwTopConstraint?.constant = 50
view.layoutIfNeeded()
}
如上所示,如果我们希望更改影响视图的约束,我们需要在 ViewController 中存储对它的引用。而且,由于在大多数现代应用程序中,我们通常有多个移动部件,这将很快变得繁琐,并使 UIViewController
充斥着对影响不同视图的不同约束的引用
private var vw1TopConstraint: NSLayoutConstraint?
private var vw2LeadingConstraint: NSLayoutConstraint?
...
private var vw5LeadingConstraint: NSLayoutConstraint?
另一个例子是停用、更改然后重新激活约束,这同样需要我们在 UIView
/UIViewController
中的某个地方存储对该约束的引用,然后我们必须在激活之前解包它。
使用 ConstraintsHolder 框架可以避免这种样板代码和容易出错的模式
private let vw = UIView()
...
override func viewDidLoad() {
super.viewDidLoad()
vw.translatesAutoResizingMaskIntoConstraints = false
view.addSubview(vw)
vw.updateConstraints { holder in
holder.top = vw.topAnchor.constraint(equalTo: view.topAnchor, constant: 20)
holder.activate([
\.top
])
}
}
...
func changeConstraint() {
vw.updateConstraints { holder in
holder.top?.constant = 50
view.layoutIfNeeded()
}
}
这将执行完全相同的操作,但我们不再需要在 ViewController 内部存储对 topAnchor
约束的引用,因为我们将其分配给视图的 holder
- 约束容器。
除了方便和减少样板代码之外,这种方法还有其他好处,例如
vw.updateConstraints { holder in
holder.top = vw.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20)
// fatalError: Can't assign value of ConstraintType.leading to variable of ConstraintType.top
holder.activate([
\.top
])
}
vw.updateConstraints { holder in
holder.top = vw.topAnchor.constraint(equalTo: view.topAnchor, constant: 20)
holder.activate([
\.bottom
])
// fatalError: keyPath passed to activate() contained nil value constraint
}
vw.updateConstraints { holder in
holder.deactivate([
\.bottom
])
// fatalError: keyPath passed to deactivate() contained nil value constraint
}
vw.updateConstraints { holder in
holder.top = nil
// fatalError: top constraint must be deactivated first
}
private var vw1TopConstraint: NSLayoutConstraint?
private var vw2TopConstraint: NSLayoutConstraint?
...
override func viewDidLoad() {
super.viewDidLoad()
...
guard let vw2TopConstraint else { return }
NSLayoutConstraint.activate([
vw2TopConstraint // should've been vw1
])
}
// just an example piece of code from one of my apps
...
assetsTotalValueLabel.updateConstraints { holder in
// deactivate old constraints
holder.deactivate([
\.centerY,
\.leading
])
// replace old constraints with new
holder.centerY = assetsTotalValueLabel.centerYAnchor.constraint(equalTo: navBar.centerYAnchor)
holder.leading = assetsTotalValueLabel.leadingAnchor.constraint(equalTo: smallTitleView.trailingAnchor, constant: 10)
// activate new constraints
holder.activate([
\.centerY,
\.leading
])
}
active
约束vw.updateConstraints { holder in
let all = holder.all // [NSLayoutConstraint]
let active = holder.active() // [Constraints.ConstraintType: NSLayoutConstraint]
}
ConstraintsHolder 使用 UIViewID 作为识别 UIView
的方法,因此如果您的应用程序中的代码无条件地覆盖 accessibilityIdentifier
属性,您最好不要使用此框架。 但是,通过确保在覆盖它之前检查现有的 accessibilityIdentifier
来保持兼容性非常容易,如下所示
if let accessibilityIdentifier {
// use existing one
} else {
// safe to override
}
或者直接使用专门为此目的构建的 UIViewID 包。