jsdw_ios/QuickLocation/Tool/AutoLayout/AutoLayout+UIView.swift

511 lines
20 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// 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
}
}
}