// // AutoLayout_UIView.swift // AutoLayoutSwiftDemo // // Created by Johnhao on 2022/1/6. // import UIKit // MARK: - AutoLayout extension UIView { // MARK: - Private private enum Key { /// 存储约束key internal static var lastConstraint = "lastConstraint" /// 存储约束key internal static var constraints = "constraints" /// jhLayoutChain internal static var jhLayoutChain = "jhLayoutChain" /// collapsed internal static var collapsed = "collapsed" /// autoCollapsed internal static var autoCollapse = "autoCollapse" /// hiddenCollapsed internal static var hiddenCollapse = "hiddenCollapse" } /// 所有的约束 private var jh_allConstraints: [NSLayoutConstraint] { get { jh_layoutConstraints.values.compactMap { $0 } } } /// 所有约束key-value private var jh_layoutConstraints: [String: NSLayoutConstraint] { get { guard let constraints = objc_getAssociatedObject(self, &Key.constraints) as? [String: NSLayoutConstraint] else { let constraints = [String: NSLayoutConstraint]() objc_setAssociatedObject(self, &Key.constraints, constraints, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) return constraints } return constraints } set { objc_setAssociatedObject(self, &Key.constraints, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } @discardableResult private func jh_constrain(attribute: NSLayoutConstraint.Attribute, toSuperView: UIView?, constant: CGFloat, relation: NSLayoutConstraint.Relation) -> NSLayoutConstraint { var offset = constant var new_relation = relation if attribute == .bottom || attribute == .right || attribute == .trailing { offset = -constant if relation == .lessThanOrEqual { new_relation = .greaterThanOrEqual } else if relation == .greaterThanOrEqual { new_relation = .lessThanOrEqual } } return jh_constrain(attribute: attribute, toAttribute: attribute, otherView: toSuperView, multiplier: 1.0, constant: offset, relation: new_relation) } @discardableResult private func jh_constrain(attribute: NSLayoutConstraint.Attribute, toAttribute: NSLayoutConstraint.Attribute, otherView: UIView?, multiplier: CGFloat, constant: CGFloat, relation: NSLayoutConstraint.Relation) -> NSLayoutConstraint { self.translatesAutoresizingMaskIntoConstraints = false let layoutKey = "\(attribute.rawValue)-\(relation.rawValue)-\((otherView?.hash ?? 0))-\(toAttribute.rawValue)-\(multiplier)" // 判断是否存在相同约束 guard let constraint = self.jh_layoutConstraints[layoutKey] else { // 不存在就新建 let constraint = NSLayoutConstraint(item: self, attribute: attribute, relatedBy: relation, toItem: otherView, attribute: toAttribute, multiplier: multiplier, constant: constant) self.jh_layoutConstraints[layoutKey] = constraint self.jh_lastConstraint = constraint constraint.isActive = true return constraint } // 存在就更新 constraint.constant = constant self.jh_lastConstraint = constraint constraint.isActive = true return constraint } } // MARK: - Public extension UIView { @discardableResult public func jh_constrainAttribute(_ attribute: NSLayoutConstraint.Attribute, toAttribute: NSLayoutConstraint.Attribute, of otherView: UIView) -> NSLayoutConstraint { jh_constrainAttribute(attribute, toAttribute: toAttribute, of: otherView, offset: 1.0) } @discardableResult public func jh_constrainAttribute(_ attribute: NSLayoutConstraint.Attribute, toAttribute: NSLayoutConstraint.Attribute, of otherView: UIView, offset: CGFloat) -> NSLayoutConstraint { jh_constrainAttribute(attribute, toAttribute: toAttribute, of: otherView, offset: offset, relation: .equal) } @discardableResult public func jh_constrainAttribute(_ attribute: NSLayoutConstraint.Attribute, toAttribute: NSLayoutConstraint.Attribute, of otherView: UIView?, offset: CGFloat, relation: NSLayoutConstraint.Relation) -> NSLayoutConstraint { jh_constrain(attribute: attribute, toAttribute: toAttribute, otherView: otherView, multiplier: 1.0, constant: offset, relation: relation) } @discardableResult public func jh_constrainAttribute(_ attribute: NSLayoutConstraint.Attribute, toAttribute: NSLayoutConstraint.Attribute, of otherView: UIView, multiplier: CGFloat) -> NSLayoutConstraint { jh_constrainAttribute(attribute, toAttribute: toAttribute, of: otherView, multiplier: multiplier, relation: .equal) } @discardableResult public func jh_constrainAttribute(_ attribute: NSLayoutConstraint.Attribute, toAttribute: NSLayoutConstraint.Attribute, of otherView: UIView, multiplier: CGFloat, relation: NSLayoutConstraint.Relation) -> NSLayoutConstraint { jh_constrain(attribute: attribute, toAttribute: toAttribute, otherView: otherView, multiplier: multiplier, constant: 0.0, relation: relation) } @discardableResult public func jh_constraint(_ attribute: NSLayoutConstraint.Attribute, toAttribute: NSLayoutConstraint.Attribute, otherView: UIView?, offset: CGFloat, multiplier: CGFloat, relation: NSLayoutConstraint.Relation) -> NSLayoutConstraint { jh_constrain(attribute: attribute, toAttribute: toAttribute, otherView: otherView, multiplier: multiplier, constant: offset, relation: relation) } } // MARK: - Constraint extension UIView { /// 最新一条约束 public var jh_lastConstraint: NSLayoutConstraint? { set { objc_setAssociatedObject(self, &Key.lastConstraint, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } get { objc_getAssociatedObject(self, &Key.lastConstraint) as? NSLayoutConstraint } } public func jh_constraint(_ attribute: NSLayoutConstraint.Attribute, toAttribute: NSLayoutConstraint.Attribute, otherView: UIView?, multiplier: CGFloat = 1.0, relation: NSLayoutConstraint.Relation) -> NSLayoutConstraint? { let layoutKey = "\(attribute.rawValue)-\(relation.rawValue)-\((otherView?.hash ?? 0))-\(toAttribute.rawValue)-\(multiplier)" return jh_layoutConstraints[layoutKey] } } // MARK: - All extension UIView { /// 移除所有约束 public func jh_removeAllConstraints() { NSLayoutConstraint.deactivate(jh_allConstraints) jh_layoutConstraints.removeAll() translatesAutoresizingMaskIntoConstraints = true jh_lastConstraint = nil } } // MARK: - Compression extension UIView { /// 抗水平方向压缩 public var jh_compressionHorizontal: UILayoutPriority { get { contentCompressionResistancePriority(for: .horizontal) } set { setContentCompressionResistancePriority(newValue, for: .horizontal) } } /// 抗垂直方向压缩 public var jh_compressionVertical: UILayoutPriority { get { contentCompressionResistancePriority(for: .vertical) } set { setContentCompressionResistancePriority(newValue, for: .vertical) } } /// 抗水平方向拉伸 public var jh_huggingHorizontal: UILayoutPriority { get { contentHuggingPriority(for: .horizontal) } set { setContentHuggingPriority(newValue, for: .horizontal) } } /// 抗垂直方向拉伸 public var jh_huggingVertical: UILayoutPriority { get { contentHuggingPriority(for: .vertical) } set { setContentHuggingPriority(newValue, for: .vertical) } } } // MARK: - Collapse extension UIView { /// 设置视图是否伸缩,默认false,true时常量值为0,false时常量值为原始值 public var jh_collapsed: Bool { set { jh_allConstraints.forEach { if newValue { $0.constant = 0 } else { $0.constant = $0.jh_originalConstraint } } objc_setAssociatedObject(self, &Key.collapsed, newValue, .OBJC_ASSOCIATION_ASSIGN) } get { objc_getAssociatedObject(self, &Key.collapsed) as? Bool ?? false } } /// 设置视图是否自动收缩,如image为nil,text为nil、@""时自动收缩,默认false public var jh_autoCollapse: Bool { set { objc_setAssociatedObject(self, &Key.autoCollapse, newValue, .OBJC_ASSOCIATION_ASSIGN) } get { objc_getAssociatedObject(self, &Key.autoCollapse) as? Bool ?? false } } /// 设置视图是否隐藏时自动收缩、显示时自动展开,默认false public var jh_hiddenCollapse: Bool { set { objc_setAssociatedObject(self, &Key.hiddenCollapse, newValue, .OBJC_ASSOCIATION_ASSIGN) } get { objc_getAssociatedObject(self, &Key.hiddenCollapse) as? Bool ?? false } } } // MARK: - Axis extension UIView { @discardableResult public func jh_alignCenterToSuperView(with offset: CGPoint = .zero) -> [NSLayoutConstraint] { var constrints = [NSLayoutConstraint]() constrints.append(jh_alignAxisToSuperView(.centerX, withOffset: offset.x)) constrints.append(jh_alignAxisToSuperView(.centerY, withOffset: offset.y)) return constrints } @discardableResult public func jh_alignAxisToSuperView(_ axis: NSLayoutConstraint.Attribute, withOffset: CGFloat = 0) -> NSLayoutConstraint { jh_constrain(attribute: axis, toSuperView: self.superview, constant: withOffset, relation: .equal) } @discardableResult public func jh_alignAxis(_ axis: NSLayoutConstraint.Attribute, to otherView: UIView, offset: CGFloat = 0, multiplier: CGFloat = 1.0) -> NSLayoutConstraint { jh_constrain(attribute: axis, toAttribute: axis, otherView: otherView, multiplier: multiplier, constant: offset, relation: .equal) } } // MARK: - Edge extension UIView { @discardableResult public func jh_pinEdgesToSuperView() -> [NSLayoutConstraint] { jh_pinEdgesToSuperView(.zero) } @discardableResult public func jh_pinEdgesToSuperView(_ insets: UIEdgeInsets) -> [NSLayoutConstraint] { var constraints = [NSLayoutConstraint]() constraints.append(jh_pinEdgeToSuperView(.top, inset: insets.top)) constraints.append(jh_pinEdgeToSuperView(.bottom, inset: insets.bottom)) constraints.append(jh_pinEdgeToSuperView(.leading, inset: insets.left)) constraints.append(jh_pinEdgeToSuperView(.trailing, inset: insets.right)) return constraints } @discardableResult public func jh_pinEdgesToSuperView(_ insets: UIEdgeInsets, excludingEdge: NSLayoutConstraint.Attribute) -> [NSLayoutConstraint] { var constraints = [NSLayoutConstraint]() if excludingEdge != .top { constraints.append(jh_pinEdgeToSuperView(.top, inset: insets.top)) } if excludingEdge != .bottom { constraints.append(jh_pinEdgeToSuperView(.bottom, inset: insets.bottom)) } if excludingEdge != .leading && excludingEdge != .left { constraints.append(jh_pinEdgeToSuperView(.leading, inset: insets.left)) } if excludingEdge != .trailing && excludingEdge != .right { constraints.append(jh_pinEdgeToSuperView(.trailing, inset: insets.right)) } return constraints } @discardableResult public func jh_pinEdgesToSuperViewHorizontal(inset: CGFloat = 0) -> [NSLayoutConstraint] { var constraints = [NSLayoutConstraint]() constraints.append(jh_pinEdgeToSuperView(.leading, inset: inset)) constraints.append(jh_pinEdgeToSuperView(.trailing, inset: inset)) return constraints } @discardableResult public func jh_pinEdgesToSuperViewVertical(inset: CGFloat = 0) -> [NSLayoutConstraint] { var constraints = [NSLayoutConstraint]() constraints.append(jh_pinEdgeToSuperView(.top, inset: inset)) constraints.append(jh_pinEdgeToSuperView(.bottom, inset: inset)) return constraints } @discardableResult public func jh_pinEdgeToSuperView(_ edge: NSLayoutConstraint.Attribute) -> NSLayoutConstraint { jh_pinEdgeToSuperView(edge, inset: 0.0) } @discardableResult public func jh_pinEdgeToSuperView(_ edge: NSLayoutConstraint.Attribute, inset: CGFloat) -> NSLayoutConstraint { jh_pinEdgeToSuperView(edge, inset: inset, relation: .equal) } @discardableResult public func jh_pinEdgeToSuperView(_ edge: NSLayoutConstraint.Attribute, inset: CGFloat, relation: NSLayoutConstraint.Relation) -> NSLayoutConstraint { jh_constrain(attribute: edge, toSuperView: self.superview, constant: inset, relation: relation) } @discardableResult public func jh_pinEdge(_ edge: NSLayoutConstraint.Attribute, toEdge: NSLayoutConstraint.Attribute, of otherView: UIView) -> NSLayoutConstraint { jh_pinEdge(edge, toEdge: toEdge, of: otherView, offset: 0.0) } @discardableResult public func jh_pinEdge(_ edge: NSLayoutConstraint.Attribute, toEdge: NSLayoutConstraint.Attribute, of otherView: UIView, offset: CGFloat) -> NSLayoutConstraint { jh_pinEdge(edge, toEdge: toEdge, of: otherView, offset: offset, relation: .equal) } @discardableResult public func jh_pinEdge(_ edge: NSLayoutConstraint.Attribute, toEdge: NSLayoutConstraint.Attribute, of otherView: UIView, offset: CGFloat, relation: NSLayoutConstraint.Relation) -> NSLayoutConstraint { jh_constrainAttribute(edge, toAttribute: toEdge, of: otherView, offset: offset, relation: relation) } } // MARK: - Dimension extension UIView { @discardableResult public func jh_setDimensionToSize(_ size: CGSize) -> [NSLayoutConstraint] { var constraints = [NSLayoutConstraint]() constraints.append(jh_setDimension(.width, toSize: size.width)) constraints.append(jh_setDimension(.height, toSize: size.height)) return constraints } @discardableResult public func jh_setDimension(_ dimension: NSLayoutConstraint.Attribute, toSize: CGFloat = 0, relation: NSLayoutConstraint.Relation = .equal) -> NSLayoutConstraint { jh_constrain(attribute: dimension, toAttribute: .notAnAttribute, otherView: nil, multiplier: 1.0, constant: toSize, relation: relation) } @discardableResult public func jh_matchDimension(_ dimension: NSLayoutConstraint.Attribute, toDimension: NSLayoutConstraint.Attribute, ofView otherView: UIView, offset: CGFloat = 0, relation: NSLayoutConstraint.Relation = .equal) -> NSLayoutConstraint { jh_constrainAttribute(dimension, toAttribute: toDimension, of: otherView, offset: offset, relation: relation) } @discardableResult public func jh_matchDimension(_ dimension: NSLayoutConstraint.Attribute, toDimension: NSLayoutConstraint.Attribute, multiplier: CGFloat = 1.0, relation: NSLayoutConstraint.Relation = .equal) -> NSLayoutConstraint { jh_constrainAttribute(dimension, toAttribute: toDimension, of: self, multiplier: multiplier, relation: relation) } @discardableResult public func jh_matchDimension(_ dimension: NSLayoutConstraint.Attribute, toDimension: NSLayoutConstraint.Attribute, ofView otherView: UIView, multiplier: CGFloat, relation: NSLayoutConstraint.Relation) -> NSLayoutConstraint { jh_constrainAttribute(dimension, toAttribute: toDimension, of: otherView, multiplier: multiplier, relation: relation) } @discardableResult public func jh_matchDimension(_ dimension: NSLayoutConstraint.Attribute, toDimension: NSLayoutConstraint.Attribute, ofView otherView: UIView, offset: CGFloat, multiplier: CGFloat, relation: NSLayoutConstraint.Relation) -> NSLayoutConstraint { jh_constrain(attribute: dimension, toAttribute: toDimension, otherView: otherView, multiplier: multiplier, constant: offset, relation: relation) } } // MARK: - LayoutChain extension UIView { public var layoutChain: AutoLayoutSwift { get { guard let layout = objc_getAssociatedObject(self, &Key.jhLayoutChain) as? AutoLayoutSwift else { let layout = AutoLayoutSwift(view: self) objc_setAssociatedObject(self, &Key.jhLayoutChain, layout, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) return layout } return layout } } }