一套小型实用程序,旨在消除 autolayout API 中最常见的一些不足之处。其中包含几个不同但相关的实用程序集。
操作约束,尤其是在设置约束时,其开发者体验并不总是那么顺畅。虽然主要是语法糖,但此处提供的 @inlinable
实用程序允许编写更简单但又不失可读性的代码来设置约束。
例如,如果您想以高优先级设置高度约束,而不是必须编写以下代码:
let heightConstraint = view.heightAnchor.constraint(equalToConstant: Self.height)
heightConstraint.priority = .defaultHigh
heightConstraint.isActive = true
您可以像这样操作(假设之后我们不需要该约束):
view.heightAnchor.constraint(equalToConstant: Self.height).priority(.defaultHigh).activate()
每次有人在代码中构建 UI 并需要将其子视图与父视图边缘对齐时,都会感到非常痛苦。不再需要了。只需使用其中的实用程序一次性生成所有约束,或者生成满足您需求的任何约束子集。然后使用约束实用程序或属性包装器来激活它们。
请记住,构建这些实用程序的目的是为了处理使用常规 API 比需要的更痛苦的情况。如果您在这里寻找某些东西但没有找到,很可能我们已经确定布局锚点 API 本身已经做得足够好了。
例如,如果我们需要一个视图靠在其父视图的安全区域的左右和顶部,而不是编写以下代码:
contentView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(contentView)
NSLayoutConstraint.activate([
contentView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
contentView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
view.safeAreaLayoutGuide.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
])
我们可以编写以下代码:
contentView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(contentView)
NSLayoutConstraint.activate(contentView.constraintsAgainstEnclosing(
layoutArea: view.safeAreaLayoutGuide,
edges: [.top, .leading, .trailing]
))
或者更好的是,利用整个包:
view.add(subview: contentView)
contentView.constraintsAgainstEnclosing(
layoutArea: view.safeAreaLayoutGuide,
edges: [.top, .leading, .trailing]
).activate()
在 autolayout 环境中,布局更改是通过添加和删除约束或通过调整现有约束的属性(constant
或 priority
)来完成的。在这两种情况下,都需要在框架自动管理之外保留对这些约束的引用。
提供了一对属性包装器,ActiveConstraint
(用于单个约束)和 @ActiveConstraints
(用于约束数组),它们将确保放置在其中的任何约束都被激活,并且从中删除的任何约束都被停用。这可以平滑 UI 需要时的约束更改。
例如,假设我们有一个 contentView
约束到上面的 headerView
,但我们希望在解除动画发生时保持其高度。我们可以这样做:
[...]
@ActiveConstraint
private var contentHeightHolder: NSLayoutConstraint?
func setupUI() {
[...]
contentHeightHolder = contentView.topAnchor.constraint(equalTo: headerView.bottomAnchor)
[...]
}
func prepareForDismissal() {
[...]
// Layout is supposed to stable here, don't grab your frames or bounds otherwise kids!
contentHeightHolder = contentView.heightAnchor.constraint(equalToConstant: heightHolder.frame.height)
[...]
}
管理子视图的 translatesAutoResizingMaskIntoConstraints
标志是父视图的责任,所有较新的容器 API 都会这样做(例如 UIStackView.addArrangedSubview
等)。但是,由于历史原因,原始的视图树管理 API 不会触及该标志,这会导致人们忘记手动关闭它而导致错误。
提供了一组用于框架视图层次结构管理的包装器。它们在每个平台上都是不同的,以便允许 1 对 1 替换现有逻辑。基本上,用新方法替换旧方法,并从 UI 设置逻辑中删除所有提及 translatesAutoResizingMaskIntoConstraints
的地方。
可以安全地假设,如今手动布局在 UI 逻辑中是一个例外,但如果人们忘记使用这些实用程序,那么拥有这些实用程序也无济于事。为了鼓励开发者避免使用旧方法并使用新方法,您可以将以下规则添加到您的 linter 配置文件中:
avoid_manually_disabling_TARMiC:
regex: 'translatesAutoresizingMaskIntoConstraints = false'
message: "Do not manually turn translatesAutoresizingMaskIntoConstraints off, use the managed view hierarchy methods add(subview:) and insert(subview:) instead"
match_kinds: identifier
avoid_addsubview:
regex: 'addSubview\('
message: "Do not call `addSubview(...)` directly, use the `add(subview:...)` wrappers instead"
match_kinds: identifier
avoid_insertsubview:
regex: 'insertSubview\('
message: "Do not call `insertSubview(...)` directly, use the `insert(subview:...)` wrappers instead"
match_kinds: identifier
与任何其他 linter 规则一样,它们可以在有意义的地方暂时禁用。