ConstraintsHolder 是一个基于现代 Anchors API 的轻量级 Auto Layout 框架,它通过抽象化约束的存储来简化约束的使用。 当你需要频繁更改约束时,例如在具有动态 UI 的应用程序中,它尤其有用。

安装

要通过 Swift Package Manager (SPM) 安装,只需执行以下操作

  1. 从 Xcode 菜单中选择 File > Add Package Dependency
  2. 粘贴 URL https://github.com/leofriskey/ConstraintsHolder

为什么使用 ConstraintsHolder

考虑以下常见示例

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 - 约束容器。

除了方便和减少样板代码之外,这种方法还有其他好处,例如

  1. 不易出错的约束赋值
    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
        ])
    }
  1. 不易出错的约束激活/停用
    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
    }
  1. 不易出错的约束移除
vw.updateConstraints { holder in
    holder.top = nil
    // fatalError: top constraint must be deactivated first
}
  1. 由于框架使用 keyPaths,您不会像下面的示例中那样,错误地激活/停用另一个不影响您的视图的约束
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
    ])
}
  1. 简洁而美观的代码。 从任何地方更新视图绑定的约束!
// 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
    ])
}
  1. 返回所有视图设置的约束,或仅返回 active 约束
vw.updateConstraints { holder in
    let all = holder.all // [NSLayoutConstraint]
    let active = holder.active() // [Constraints.ConstraintType: NSLayoutConstraint]
}
  1. 当视图从视图层级结构中移除时,约束会自动清除 - 因此您不必自己执行此操作!

注意

ConstraintsHolder 使用 UIViewID 作为识别 UIView 的方法,因此如果您的应用程序中的代码无条件地覆盖 accessibilityIdentifier 属性,您最好不要使用此框架。 但是,通过确保在覆盖它之前检查现有的 accessibilityIdentifier 来保持兼容性非常容易,如下所示

if let accessibilityIdentifier {
    // use existing one
} else {
    // safe to override
}

或者直接使用专门为此目的构建的 UIViewID 包。