Pin

📌 一个使 AutoLayout 使用起来更简单的微型库。

这个库是为那些不想使用像 SnapKit 这样的大型库,但又觉得标准 NSLayoutConstraint 创建方式过于冗长的人准备的。

使用 Pin 的例子 😸

import UIKit
import Pin

class CurrencyCell: UITableViewCell {
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        contentView.addSubview(bodyView)
        bodyView.addSubviews([flagLabel, titleLabel, codeLabel])
        
        Pin.activate([
            bodyView.pin.horizontally(offset: 15).vertically(),
            flagLabel.pin.start().size(36).centerY(),
            titleLabel.pin.after(flagLabel, offset: 15).vertically(),
            codeLabel.pin.after(titleLabel, offset: 15).end().vertically()
        ])
    }
    
    let bodyView = UIView()
    let flagLabel = UILabel()
    let titleLabel = UILabel()
    let codeLabel = UILabel() 
    
}

原始代码

不使用 Pin 的例子 🙀

import UIKit

class CurrencyCell: UITableViewCell {
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        bodyView.translatesAutoresizingMaskIntoConstraints = false
        flagLabel.translatesAutoresizingMaskIntoConstraints = false
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        codeLabel.translatesAutoresizingMaskIntoConstraints = false
        
        contentView.addSubview(bodyView)
        bodyView.addSubview(flagLabel)
        bodyView.addSubview(titleLabel)
        bodyView.addSubview(codeLabel)
        
        NSLayoutConstraint.activate([
            bodyView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 15),
            bodyView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -15),
            bodyView.topAnchor.constraint(equalTo: contentView.topAnchor),
            bodyView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
            
            flagLabel.leadingAnchor.constraint(equalTo: bodyView.leadingAnchor),
            flagLabel.centerYAnchor.constraint(equalTo: bodyView.centerYAnchor),
            flagLabel.widthAnchor.constraint(equalToConstant: 36),
            flagLabel.heightAnchor.constraint(equalToConstant: 36),
            
            titleLabel.leadingAnchor.constraint(equalTo: flagLabel.trailingAnchor, constant: 15),
            titleLabel.topAnchor.constraint(equalTo: bodyView.topAnchor),
            titleLabel.bottomAnchor.constraint(equalTo: bodyView.bottomAnchor),
            
            codeLabel.leadingAnchor.constraint(equalTo: titleLabel.trailingAnchor, constant: 15),
            codeLabel.trailingAnchor.constraint(equalTo: bodyView.trailingAnchor),
            codeLabel.topAnchor.constraint(equalTo: bodyView.topAnchor),
            codeLabel.bottomAnchor.constraint(equalTo: bodyView.bottomAnchor)
        ])
    }
    
    let bodyView = UIView()
    let flagLabel = UILabel()
    let titleLabel = UILabel()
    let codeLabel = UILabel() 
    
}

方法

// titleLabel.leading == titleLabel.superview.leading
// Use addSubview(titleLabel) before it
titleLabel.pin.start()

// titleLabel.leading == iconView.leading
titleLabel.pin.start(iconView)

// titleLabel.leading == iconView.leading + 10
titleLabel.pin.start(iconView, offset: 10)

// titleLabel.leading == titleLabel.superview.safeAreaLayoutGuide.leading
titleLabel.pin.start(safe: true)

// titleLabel.trailing == titleLabel.superview.trailing - 10
titleLabel.pin.end(offset: -10)

// titleLabel.top == titleLabel.superview.top
titleLabel.pin.top()

// titleLabel.bottom == titleLabel.superview.bottom
titleLabel.pin.bottom()

// titleLabel.leading == titleLabel.superview.leading + 10
// titleLabel.trailing == titleLabel.superview.trailing - 10
titleLabel.pin.horizontally(offset: 10)

// titleLabel.top == titleLabel.superview.top
// titleLabel.bottom == titleLabel.superview.bottom
titleLabel.pin.vertically()

// contentView.leading == contentView.superview.leading + 10
// contentView.trailing == contentView.superview.trailing - 10
// contentView.top == contentView.superview.top + 10
// contentView.bottom == contentView.superview.bottom - 10
contentView.all(offset: 10)

// titleLabel.leading == iconView.trailing + 10
titleLabel.pin.after(iconView, offset: 10)

// iconView.trailing == titleLabel.leading - 10
iconView.pin.before(titleLabel, offset: -10)

// titleLabel.top == navigationBar.bottom + 10
titleLabel.pin.below(navigationBar, offset: 10)

// titleLabel.width == 200
titleLabel.pin.width(200)

// titleLabel.height == 20
titleLabel.pin.height(20)

// codeLabel.width == titleLabel.width
codeLabel.pin.width(titleLabel)

// codeLabel.height == titleLabel.height
codeLabel.pin.height(titleLabel)

// codeLabel.width <= titleLabel.width
codeLabel.pin.add(
    attr: .width,
    relation: .lessThanOrEqual,
    to: titleLabel,
    attr: .width
)

激活和停用

let top = titleLabel.pin.top()
top.activate()
top.deactivate()

激活约束数组比单独激活每个约束更有效率。

Pin.activate([
    bodyView.pin.horizontally(offset: 15).vertically(),
    flagLabel.pin.start().size(36).centerY(),
    titleLabel.pin.after(flagLabel, offset: 15).vertically(),
    codeLabel.pin.after(titleLabel, offset: 15).end().vertically()
])

安全区域

override func viewDidLoad() {
    super.viewDidLoad()
    view.addSubView(toolBar)
    toolBar.pin
        .start(safe: true)
        .end(safe: true)
        .top(safe: true)
        .height(55)
        .activate()
}

主体

// Add body (bodyView fill view)
view.addBody(bodyView)
// Add body with safe area
view.addBody(bodyView, safe: true)
// Add body with insets 15
view.addBody(bodyView, insets: .all(15))
// Add body with horizontal insets 15
view.addBody(bodyView, insets: .horizontal(15))

优先级

// Set priority for each constraint
titleLabel.pin
    .start().priority(.defaultHeight)
    .end().priority(.defaultLow)

// Set priority for all constraints
titleLabel.pin.start().end().priorityForAll(.defaultHeight)

访问 NSLayoutConstraint

let start = titleLabel.pin.start().constraints.last
start.constant = 30
start.isActive = true

扩展

你可以添加自己的扩展

extension Pin {
    
    public func horizontallyBetween(
        _ first: UIView,
        _ second: UIView,
        offset: CGFloat = 0
    ) -> Self {
        self.after(first, offset: offset)
            .before(second, offset: -offset)
    }
    
    public func verticallyBetween(
        _ first: UIView,
        _ second: UIView,
        offset: CGFloat = 0
    ) -> Self {
        self.add(attr: .top, to: first, attr: .bottom, constant: offset)
            .add(attr: .bottom, to: second, attr: .top, constant: -offset)
    }
    
}

从右到左的语言

方法 start(), end(), after(), before() 默认支持 rtl 语言。如果你想强制方向,可以使用

semanticContentAttribute = .forceLeftToRight

Swift Package Manager

https://github.com/mezhevikin/Pin.git