AutolayoutExtension

一个轻量级、简洁、声明式的 Autolayout 语法。(⚠️需要 Swift 5.7)

概述

我们都使用 Autolayout 在 UIKit 中排列和定位 UI 组件。 但是用代码实现它相当考验大脑,需要想象这段代码在屏幕上的呈现方式。

我们需要考虑按钮、标签、各种控件、子视图。 所有 UI 组件及其之间的关系,父级、子级、兄弟、安全区域、布局指南、尺寸类 🤯。

让我们尝试从 SwiftUI 和一个不太为人熟知的朋友 Swift KeyPaths 中汲取一些灵感,使事情变得更简单一些。

要点

我们都写过这样的代码。

self.view.addSubview(otherView)
otherView.translatesAutoresizingMaskIntoConstraints = true

otherView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
otherView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -16).isActive = true
...

在上面的代码中,仅仅为了将 otherViewleadingAnchor 相对于 view 添加约束,整行代码就非常冗长和繁琐。

otherView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true

如果我们只用以下方式编写相同的代码会怎样?

self.view.anchor(otherView) {
    EqualTo(\.leadingAnchor)
    EqualTo(\.trailingAnchor).constant(-16)
}

一目了然,更加清晰、简洁和易读。

映射

原生 AutolayoutExtension 锚点类型
.constraint(equalTo:) EqualTo (等于) NSLayoutAnchor<Axis>
.constraint(equalTo:constant) EqualToConstant (等于常量) NSLayoutDimension
.constraint(greaterThanOrEqualTo:) GreaterThanOrEqualTo (大于等于) NSLayoutAnchor<Axis>
.constraint(greaterThanOrEqualTo:constant) GreaterThanOrEqualToConstant (大于等于常量) NSLayoutDimension
.constraint(lessThanOrEqualTo:) LessThanOrEqualTo (小于等于) NSLayoutAnchor<Axis>
.constraint(lessThanOrEqualTo:Constant) LessThanOrEqualToConstant (小于等于常量) NSLayoutDimension

修饰符

有 3 个内置的约束修饰符可用。

  1. .constant(_ constant: CGFloat)
  2. .priority(_ priority : UILayoutPriority)
  3. .id<ID: Hashable>(_ id: ID)

示例

import AutolayoutExtension
import UIKit

class AView: UIView {
    var condition: Bool = false {
        didSet {
            self.updateLayout()
        }
    }
    
    private var labelTopConstraint: NSLayoutConstraint?
    private var imageBottomConstraint: NSLayoutConstraint?
    
    private lazy var titleLabel: UILabel = {
        let label = UILabel()
        return label
    }()
    
    private lazy var imageView: UIImageView = {
        let imageView = UIImageView()
        return imageView
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        configureSubviews()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        configureSubviews()
    }
    
    private func configureSubviews() {
        self.addSubview(titleLabel)
        self.addSubview(imageView)
        
        let topAnchorID = UUID()
        let bottomAnchorID = UUID()
        
        let labelConstraints = self.constraint(titleLabel) {
            EqualTo(\.leadingAnchor)
            EqualTo(\.trailingAnchor)
            EqualTo(\.topAnchor)
                .constant(24)
                .id(topAnchorID)
            EqualToConstant(\.heightAnchor, constant: 32)
        }
        
        let imageConstraints = self.constraint(imageView) {
            EqualTo(\.centerXAnchor)
            GreaterThanOrEqualTo(\.leadingAnchor)
                .constant(12)
            EqualTo(\.topAnchor, with: titleLabel.bottomAnchor)
                .constant(12)
            EqualTo(\.bottomAnchor)
                .priority(.defaultHigh)
                .id(bottomAnchorID)
        }
        
        self.labelTopConstraint = labelConstraints[topAnchorID]
        self.imageBottomConstraint = imageConstraints[bottomAnchorID]
    }
    
    private func updateLayout() {
        self.labelTopConstraint?.constant = condition ? 24 : 32
        self.imageBottomConstraint?.constant = condition ? 24 : 0
        self.layoutIfNeeded()
    }
}