511 lines
20 KiB
Swift
511 lines
20 KiB
Swift
//
|
||
// 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
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|