最近入职新公司,发现Swift项目并没有引入任何的布局第三方框架,导致还在用最原始的NSLayoutConstraint布局
addSubview(titleLabel)
addSubview(iconView)
NSLayoutConstraint.activate([
iconView.topAnchor.constraint(equalTo: topAnchor, constant: Constant.iconTopMargin),
iconView.leftAnchor.constraint(equalTo: leftAnchor, constant: Constant.iconLeftMargin),
iconView.rightAnchor.constraint(equalTo: rightAnchor, constant: -Constant.iconLeftMargin),
titleLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -Constant.titleBottomMargin),
titleLabel.leftAnchor.constraint(equalTo: leftAnchor, constant: Constant.titleLeftMargin),
titleLabel.rightAnchor.constraint(equalTo: rightAnchor, constant: -Constant.titleLeftMargin),
])
说实话看得我头皮发麻,真的感觉生存在原始社会的感觉。但由于公司引入第三方框架很麻烦,要多方评审,才能引入,于是有了自己封装一个简单的NSLayoutConstraint 布局扩展类似于 Masonry 的简洁 API 的扩展。
以下封装使得我们可以使用更简洁的语法来设置约束。我们将 Anchor 相关的使用替换为更直观的属性访问,同时为 UIView 添加 bottom 和 centerX 等属性。这种方法使得使用者可以更自然地设置约束。以下是扩展实现:
NSLayoutConstraint 扩展
import UIKit
// MARK: - UIView Extension
extension UIView {
@discardableResult
func makeConstraints(_ constraints: (ConstraintMaker) -> Void) -> [NSLayoutConstraint] {
let maker = ConstraintMaker(view: self)
constraints(maker)
let activatedConstraints = maker.constraints
NSLayoutConstraint.activate(activatedConstraints)
return activatedConstraints
}
// MARK: - Properties for Constraints
var top: NSLayoutYAxisAnchor {
return self.topAnchor
}
var bottom: NSLayoutYAxisAnchor {
return self.bottomAnchor
}
var leading: NSLayoutXAxisAnchor {
return self.leadingAnchor
}
var trailing: NSLayoutXAxisAnchor {
return self.trailingAnchor
}
var centerX: NSLayoutXAxisAnchor {
return self.centerXAnchor
}
var centerY: NSLayoutYAxisAnchor {
return self.centerYAnchor
}
}
// MARK: - ConstraintMaker Class
class ConstraintMaker {
private var constraints: [NSLayoutConstraint] = []
private weak var view: UIView?
init(view: UIView) {
self.view = view
}
// MARK: - Top Constraint
@discardableResult
func top(_ anchor: NSLayoutYAxisAnchor) -> Offset {
return Offset { constant in
guard let view = self.view else { return }
let constraint = view.top.constraint(equalTo: anchor, constant: constant)
self.constraints.append(constraint)
}
}
// MARK: - Bottom Constraint
@discardableResult
func bottom(_ anchor: NSLayoutYAxisAnchor) -> Offset {
return Offset { constant in
guard let view = self.view else { return }
let constraint = view.bottom.constraint(equalTo: anchor, constant: constant)
self.constraints.append(constraint)
}
}
// MARK: - Leading Constraint
@discardableResult
func leading(_ anchor: NSLayoutXAxisAnchor) -> Offset {
return Offset { constant in
guard let view = self.view else { return }
let constraint = view.leading.constraint(equalTo: anchor, constant: constant)
self.constraints.append(constraint)
}
}
// MARK: - Trailing Constraint
@discardableResult
func trailing(_ anchor: NSLayoutXAxisAnchor) -> Offset {
return Offset { constant in
guard let view = self.view else { return }
let constraint = view.trailing.constraint(equalTo: anchor, constant: constant)
self.constraints.append(constraint)
}
}
// MARK: - Center Constraints
@discardableResult
func centerX(_ anchor: NSLayoutXAxisAnchor) -> Self {
guard let view = self.view else { return self }
let constraint = view.centerX.constraint(equalTo: anchor)
constraints.append(constraint)
return self
}
@discardableResult
func centerY(_ anchor: NSLayoutYAxisAnchor) -> Self {
guard let view = self.view else { return self }
let constraint = view.centerY.constraint(equalTo: anchor)
constraints.append(constraint)
return self
}
// MARK: - Size Constraints
@discardableResult
func width(_ constant: CGFloat) -> Self {
guard let view = self.view else { return self }
let constraint = view.width.constraint(equalToConstant: constant)
constraints.append(constraint)
return self
}
@discardableResult
func height(_ constant: CGFloat) -> Self {
guard let view = self.view else { return self }
let constraint = view.height.constraint(equalToConstant: constant)
constraints.append(constraint)
return self
}
// MARK: - Aspect Ratio
@discardableResult
func aspectRatio(_ ratio: CGFloat) -> Self {
guard let view = self.view else { return self }
let constraint = view.width.constraint(equalTo: view.height, multiplier: ratio)
constraints.append(constraint)
return self
}
}
// MARK: - Offset Class
class Offset {
private let closure: (CGFloat) -> Void
init(closure: @escaping (CGFloat) -> Void) {
self.closure = closure
}
func offset(_ value: CGFloat) {
closure(value)
}
}
使用示例
现在你可以使用新的简洁语法,例如 myView.bottom 和 view.centerX,而不需要使用 Anchor。示例如下:
import UIKit
class MyViewController: UIViewController {
let myView = UIView()
let anotherView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
myView.backgroundColor = .blue
anotherView.backgroundColor = .red
myView.translatesAutoresizingMaskIntoConstraints = false
anotherView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(myView)
view.addSubview(anotherView)
// 设置 myView 的约束
myView.makeConstraints { make in
make.top(view.safeAreaLayoutGuide.top).offset(20) // 使用 offset
make.leading(view.leading).offset(20)
make.trailing(view.trailing).offset(-20)
make.height(200)
}
// 设置 anotherView 的约束
anotherView.makeConstraints { make in
make.top(myView.bottom).offset(20)
make.centerX(view.centerX) // 不需要 Anchor
make.width(150)
make.height(100)
}
}
}
小结
通过这种方式,约束的设置变得更加直观和简洁,符合现代 Swift 编程风格。你可以自由地访问 UIView 的 top、bottom、leading、trailing、centerX 和 centerY 属性,而不必显式使用 Anchor 后缀。这种封装非常灵活,适合用于各种复杂的布局场景。