1. 给谁做约束
ConstraintView
#if canImport(UIKit)
public typealias ConstraintView = UIView
#else
public typealias ConstraintView = NSView
#endif
对 ConstraintView 做扩展,定义一个snp属性
public extension ConstraintView {
var snp: ConstraintViewDSL {
return ConstraintViewDSL(view: self)
}
}
ConstraintViewDSL 定义如下:
public struct ConstraintViewDSL: ConstraintAttributesDSL {
@discardableResult
public func prepareConstraints(_ closure: (_ make: ConstraintMaker) -> Void) -> [Constraint] {
return ConstraintMaker.prepareConstraints(item: self.view, closure: closure)
}
public func makeConstraints(_ closure: (_ make: ConstraintMaker) -> Void) {
ConstraintMaker.makeConstraints(item: self.view, closure: closure)
}
public func remakeConstraints(_ closure: (_ make: ConstraintMaker) -> Void) {
ConstraintMaker.remakeConstraints(item: self.view, closure: closure)
}
public func updateConstraints(_ closure: (_ make: ConstraintMaker) -> Void) {
ConstraintMaker.updateConstraints(item: self.view, closure: closure)
}
public func removeConstraints() {
ConstraintMaker.removeConstraints(item: self.view)
}
public var contentHuggingHorizontalPriority: Float {
get {
return self.view.contentHuggingPriority(for: .horizontal).rawValue
}
nonmutating set {
self.view.setContentHuggingPriority(LayoutPriority(rawValue: newValue), for: .horizontal)
}
}
public var target: AnyObject? {
return self.view
}
internal let view: ConstraintView
internal init(view: ConstraintView) {
self.view = view
}
}
ConstraintViewDSL
提供了makeConstraints
和 updateConstraints
等方法
ConstraintViewDSL
继承于 ConstraintAttributesDSL
, ConstraintAttributesDSL
继承于 ConstraintBasicAttributesDSL
ConstraintBasicAttributesDSL
定义了基础属性,比如left
, top
, center
, width
, height
ConstraintAttributesDSL
提供了iOS8才有的一些属性,比如 leftMargin
, firstBaseline
2. 如何设置约束
ConstraintMaker
ConstraintMaker 是设置约束的入口函数
public class ConstraintMaker {
internal init(item: LayoutConstraintItem) {
self.item = item
self.item.prepare()
}
}
prepare 函数实现, 禁用了AutoresizingMask
extension LayoutConstraintItem {
internal func prepare() {
if let view = self as? ConstraintView {
view.translatesAutoresizingMaskIntoConstraints = false
}
}
}
ConstraintMaker 提供了属性的getter
方法, 如下所示
public class ConstraintMaker {
public var left: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.left)
}
public var top: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.top)
}
internal func makeExtendableWithAttributes(_ attributes: ConstraintAttributes) -> ConstraintMakerExtendable {
let description = ConstraintDescription(item: self.item, attributes: attributes)
self.descriptions.append(description)
return ConstraintMakerExtendable(description)
}
// ...
}
ConstraintMaker
里包含了一个 ConstraintDescription 数组, 保存了用户设置的各个属性, 然后返回 ConstraintMakerExtendable
ConstraintAttributes
ConstraintAttributes
是一个结构体, 遵从 OptionSet
, 因为swift的枚举没法将多个枚举项组成一个值,比如 ConstraintAttributes
中的size
,center
是由多个枚举选项组合成的。 OptionSet使用了高效的位域进行表示。
internal static let none: ConstraintAttributes = 0
internal static let left: ConstraintAttributes = ConstraintAttributes(UInt(1) << 0)
internal static let top: ConstraintAttributes = ConstraintAttributes(UInt(1) << 1)
internal static let right: ConstraintAttributes = ConstraintAttributes(UInt(1) << 2)
internal static let bottom: ConstraintAttributes = ConstraintAttributes(UInt(1) << 3)
internal static let leading: ConstraintAttributes = ConstraintAttributes(UInt(1) << 4)
internal static let trailing: ConstraintAttributes = ConstraintAttributes(UInt(1) << 5)
internal static let width: ConstraintAttributes = ConstraintAttributes(UInt(1) << 6)
internal static let height: ConstraintAttributes = ConstraintAttributes(UInt(1) << 7)
internal static let centerX: ConstraintAttributes = ConstraintAttributes(UInt(1) << 8)
internal static let centerY: ConstraintAttributes = ConstraintAttributes(UInt(1) << 9)
internal static let lastBaseline: ConstraintAttributes = ConstraintAttributes(UInt(1) << 10)
// ...
internal static let edges: ConstraintAttributes = [.horizontalEdges, .verticalEdges]
internal static let horizontalEdges: ConstraintAttributes = [.left, .right]
internal static let verticalEdges: ConstraintAttributes = [.top, .bottom]
internal static let directionalEdges: ConstraintAttributes = [.directionalHorizontalEdges, .directionalVerticalEdges]
internal static let directionalHorizontalEdges: ConstraintAttributes = [.leading, .trailing]
internal static let directionalVerticalEdges: ConstraintAttributes = [.top, .bottom]
internal static let size: ConstraintAttributes = c
internal static let center: ConstraintAttributes = [.centerX, .centerY]
center 是由 [.centerX, .centerY], size 是由 [.width, .height]
ConstraintAttributes 重载了 + += -= ==
运算符
internal func + (left: ConstraintAttributes, right: ConstraintAttributes) -> ConstraintAttributes {
return left.union(right)
}
internal func +=(left: inout ConstraintAttributes, right: ConstraintAttributes) {
left.formUnion(right)
}
internal func -=(left: inout ConstraintAttributes, right: ConstraintAttributes) {
left.subtract(right)
}
internal func ==(left: ConstraintAttributes, right: ConstraintAttributes) -> Bool {
return left.rawValue == right.rawValue
}
ConstraintMakerExtendable
ConstraintMakerExtendable
继承于ConstraintMakerRelatable
, 可以实现多个属性的链式调用
比如设置left,right,top
public class ConstraintMakerExtendable: ConstraintMakerRelatable {
public var left: ConstraintMakerExtendable {
self.description.attributes += .left
return self
}
public var top: ConstraintMakerExtendable {
self.description.attributes += .top
return self
}
}
通过重载运算符 +=
能够将 .left 驾到 ConstraintAttributes
ConstraintMakerRelatable
ConstraintMakerRelatable 作用是指定视图间的约束关系,比如常用的 equalTo
函数
@discardableResult
public func equalTo(_ other: ConstraintRelatableTarget, _ file: String = #file, _ line: UInt = #line) -> ConstraintMakerEditable {
return self.relatedTo(other, relation: .equal, file: file, line: line)
}
internal func relatedTo(_ other: ConstraintRelatableTarget, relation: ConstraintRelation,
file: String, line: UInt) -> ConstraintMakerEditable {
let related: ConstraintItem
let constant: ConstraintConstantTarget
if let other = other as? ConstraintItem {
guard other.attributes == ConstraintAttributes.none ||
other.attributes.layoutAttributes.count <= 1 ||
other.attributes.layoutAttributes == self.description.attributes.layoutAttributes ||
other.attributes == .edges && self.description.attributes == .margins ||
other.attributes == .margins && self.description.attributes == .edges ||
other.attributes == .directionalEdges && self.description.attributes == .directionalMargins ||
other.attributes == .directionalMargins && self.description.attributes == .directionalEdges else {
fatalError("Cannot constraint to multiple non identical attributes. (\(file), \(line))");
}
related = other
constant = 0.0
} else if let other = other as? ConstraintView {
related = ConstraintItem(target: other, attributes: ConstraintAttributes.none)
constant = 0.0
} else if let other = other as? ConstraintConstantTarget {
related = ConstraintItem(target: nil, attributes: ConstraintAttributes.none)
constant = other
} else if #available(iOS 9.0, OSX 10.11, *), let other = other as? ConstraintLayoutGuide {
related = ConstraintItem(target: other, attributes: ConstraintAttributes.none)
constant = 0.0
} else {
fatalError("Invalid constraint. (\(file), \(line))")
}
let editable = ConstraintMakerEditable(self.description)
editable.description.sourceLocation = (file, line)
editable.description.relation = relation
editable.description.related = related
editable.description.constant = constant
return editable
}
ConstraintRelatableTarget
ConstraintRelatableTarget 是约束视图的实例,对 ConstraintRelatableTarget 进行扩展,添加更多可支持类型
public protocol ConstraintRelatableTarget {
}
......
extension ConstraintInsets: ConstraintRelatableTarget {
}
extension ConstraintItem: ConstraintRelatableTarget {
}
extension ConstraintView: ConstraintRelatableTarget {
}
public protocol ConstraintInsetTarget: ConstraintConstantTarget {
}
extension ConstraintInsetTarget {
internal var constraintInsetTargetValue: ConstraintInsets {
if let amount = self as? ConstraintInsets {
return amount
} else if let amount = self as? Float {
return ConstraintInsets(top: CGFloat(amount), left: CGFloat(amount), bottom: CGFloat(amount), right: CGFloat(amount))
} else if let amount = self as? Double {
return ConstraintInsets(top: CGFloat(amount), left: CGFloat(amount), bottom: CGFloat(amount), right: CGFloat(amount))
} else if let amount = self as? CGFloat {
return ConstraintInsets(top: amount, left: amount, bottom: amount, right: amount)
} else if let amount = self as? Int {
return ConstraintInsets(top: CGFloat(amount), left: CGFloat(amount), bottom: CGFloat(amount), right: CGFloat(amount))
} else if let amount = self as? UInt {
return ConstraintInsets(top: CGFloat(amount), left: CGFloat(amount), bottom: CGFloat(amount), right: CGFloat(amount))
} else {
return ConstraintInsets(top: 0, left: 0, bottom: 0, right: 0)
}
}
}
ConstraintMakerEditable
ConstraintMakerEditable 继承于 ConstraintMakerPrioritizable ,主要提供 multipliedBy
, dividedBy
, inset
, offset
用来设置约束的函数
public class ConstraintMakerEditable: ConstraintMakerPrioritizable {
@discardableResult
public func multipliedBy(_ amount: ConstraintMultiplierTarget) -> ConstraintMakerEditable {
self.description.multiplier = amount
return self
}
@discardableResult
public func dividedBy(_ amount: ConstraintMultiplierTarget) -> ConstraintMakerEditable {
return self.multipliedBy(1.0 / amount.constraintMultiplierTargetValue)
}
@discardableResult
public func offset(_ amount: ConstraintOffsetTarget) -> ConstraintMakerEditable {
self.description.constant = amount.constraintOffsetTargetValue
return self
}
@discardableResult
public func inset(_ amount: ConstraintInsetTarget) -> ConstraintMakerEditable {
self.description.constant = amount.constraintInsetTargetValue
return self
}
}
ConstraintMakerPrioritizable
用来设置优先级
public class ConstraintMakerPrioritizable: ConstraintMakerFinalizable {
@discardableResult
public func priority(_ amount: ConstraintPriority) -> ConstraintMakerFinalizable {
self.description.priority = amount.value
return self
}
}
ConstraintMakerFinalizable
ConstraintMakerFinalizable 包含了 ConstraintDescription
public class ConstraintMakerFinalizable {
internal let description: ConstraintDescription
internal init(_ description: ConstraintDescription) {
self.description = description
}
}
ConstraintDescription
ConstraintDescription
提供了与约束相关的所有内容
public class ConstraintDescription {
internal let item: LayoutConstraintItem
internal var attributes: ConstraintAttributes
internal var relation: ConstraintRelation? = nil
internal var sourceLocation: (String, UInt)? = nil
internal var label: String? = nil
internal var related: ConstraintItem? = nil
internal var multiplier: ConstraintMultiplierTarget = 1.0
internal var constant: ConstraintConstantTarget = 0.0
internal var priority: ConstraintPriorityTarget = 1000.0
internal lazy var constraint: Constraint? = {
guard let relation = self.relation,
let related = self.related,
let sourceLocation = self.sourceLocation else {
return nil
}
let from = ConstraintItem(target: self.item, attributes: self.attributes)
return Constraint(
from: from,
to: related,
relation: relation,
sourceLocation: sourceLocation,
label: self.label,
multiplier: self.multiplier,
constant: self.constant,
priority: self.priority
)
}()
// MARK: Initialization
internal init(item: LayoutConstraintItem, attributes: ConstraintAttributes) {
self.item = item
self.attributes = attributes
}
}
3. 设置完后约束后如何处理
public class ConstraintMaker {
internal static func prepareConstraints(item: LayoutConstraintItem, closure: (_ make: ConstraintMaker) -> Void) -> [Constraint] {
let maker = ConstraintMaker(item: item)
closure(maker)
var constraints: [Constraint] = []
for description in maker.descriptions {
guard let constraint = description.constraint else {
continue
}
constraints.append(constraint)
}
return constraints
}
internal static func makeConstraints(item: LayoutConstraintItem, closure: (_ make: ConstraintMaker) -> Void) {
let constraints = prepareConstraints(item: item, closure: closure)
for constraint in constraints {
constraint.activateIfNeeded(updatingExisting: false)
}
}
}
通过 closure
设置多个约束,存放在ConstraintMaker的description数组中
internal func activateIfNeeded(updatingExisting: Bool = false) {
guard let item = self.from.layoutConstraintItem else {
print("WARNING: SnapKit failed to get from item from constraint. Activate will be a no-op.")
return
}
let layoutConstraints = self.layoutConstraints
if updatingExisting {
var existingLayoutConstraints: [LayoutConstraint] = []
for constraint in item.constraints {
existingLayoutConstraints += constraint.layoutConstraints
}
for layoutConstraint in layoutConstraints {
let existingLayoutConstraint = existingLayoutConstraints.first { $0 == layoutConstraint }
guard let updateLayoutConstraint = existingLayoutConstraint else {
fatalError("Updated constraint could not find existing matching constraint to update: \(layoutConstraint)")
}
let updateLayoutAttribute = (updateLayoutConstraint.secondAttribute == .notAnAttribute) ? updateLayoutConstraint.firstAttribute : updateLayoutConstraint.secondAttribute
updateLayoutConstraint.constant = self.constant.constraintConstantTargetValueFor(layoutAttribute: updateLayoutAttribute)
}
} else {
NSLayoutConstraint.activate(layoutConstraints)
item.add(constraints: [self])
}
}
4. SnapKit 优秀设计
1. 链式调用(Chaining)
SnapKit 使用链式调用风格,使得代码结构简洁流畅。通过链式调用,开发者可以对同一个约束对象进行连续设置,减少冗余代码。
借鉴点:
- 使用链式调用的设计,可以让方法调用流畅、易读,避免重复的变量声明和赋值。
- 可以在返回值中返回自身(self)或其他相关对象引用,以便在一个调用链中构建出复杂逻辑
public var left: ConstraintMakerExtendable {
self.description.attributes += .left
return self
}
链式调用配置网络请求, 链式调用适用于一些配置项的设置
class RequestBuilder {
private var url: URL?
private var timeoutInterval: TimeInterval = 60
private var headers: [String: String] = [:]
private var errors: [String] = []
func setURL(_ urlString: String) -> RequestBuilder {
if let url = URL(string: urlString) {
self.url = url
} else {
errors.append("Invalid URL")
}
return self
}
func setTimeout(_ timeout: TimeInterval) -> RequestBuilder {
if timeout > 0 {
self.timeoutInterval = timeout
} else {
errors.append("Invalid timeout value")
}
return self
}
func addHeader(key: String, value: String) -> RequestBuilder {
self.headers[key] = value
return self
}
func build() -> Result<URLRequest, [String]> {
guard errors.isEmpty else {
return .failure(errors)
}
guard let url = url else {
return .failure(["URL is missing"])
}
var request = URLRequest(url: url)
request.timeoutInterval = timeoutInterval
for (key, value) in headers {
request.addValue(value, forHTTPHeaderField: key)
}
return .success(request)
}
}
2. 使用 snp 属性作为命名空间
SnapKit 通过扩展 UIView 添加了 snp 属性,使得 UIView 可以直接使用 SnapKit 方法。这种命名空间的设计不仅提升了代码的简洁性,还防止了名称冲突。
借鉴点:
- 使用命名空间封装库方法,避免与其他库或系统方法发生冲突。
- 在命名时,选用简短且有意义的名称,使代码更加直观。
public extension ConstraintView {
var snp: ConstraintViewDSL {
return ConstraintViewDSL(view: self)
}
}