diff --git a/VDS/Extensions/UIView.swift b/VDS/Extensions/UIView.swift index 327849fc..792834ef 100644 --- a/VDS/Extensions/UIView.swift +++ b/VDS/Extensions/UIView.swift @@ -10,56 +10,203 @@ import UIKit import VDSFormControlsTokens extension UIView { - public func pin(_ view: UIView, with edges: UIEdgeInsets = UIEdgeInsets.zero) { - leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: edges.left).isActive = true - trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -edges.right).isActive = true - topAnchor.constraint(equalTo: view.topAnchor, constant: edges.top).isActive = true - bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -edges.bottom).isActive = true + public enum ConstraintIdentifier: CustomStringConvertible { + case leading, trailing, top, bottom, height, width + case custom(String) + + public var description: String { + switch self { + case .leading: return "leading" + case .trailing: return "trailing" + case .top: return "top" + case .bottom: return "bottom" + case .height: return "height" + case .width: return "width" + case .custom(let identifier): return identifier + } + } } - + + public enum ConstraintType { + case equal, lessThanOrEqual, greaterThanOrEqual + } + + public var _topConstraint: NSLayoutConstraint? { constraint(with: .top)} + public var _leadingConstraint: NSLayoutConstraint? { constraint(with: .leading)} + public var _trailingConstraint: NSLayoutConstraint? { constraint(with: .trailing)} + public var _bottomConstraint: NSLayoutConstraint? { constraint(with: .bottom)} + public var _widthConstraint: NSLayoutConstraint? { constraint(with: .width)} + public var _heightConstraint: NSLayoutConstraint? { constraint(with: .height)} + + public func constraint(with identifier: ConstraintIdentifier) -> NSLayoutConstraint? { + return constraint(with: identifier.description) + } + + public func constraint(with identifier: String) -> NSLayoutConstraint? { + return constraints.first { $0.identifier == identifier } + } + + public func removeConstraint(edges: [UIRectEdge]) { + let topConstraint: NSLayoutConstraint? = constraint(with: .top) + let leadingConstraint: NSLayoutConstraint? = constraint(with: .leading) + let trailingConstraint: NSLayoutConstraint? = constraint(with: .trailing) + let bottomConstraint: NSLayoutConstraint? = constraint(with: .bottom) + + edges.forEach { edge in + switch edge { + case .all: + if let leadingConstraint { + removeConstraint(leadingConstraint) + } + if let trailingConstraint { + removeConstraint(trailingConstraint) + } + if let topConstraint { + removeConstraint(topConstraint) + } + if let bottomConstraint { + removeConstraint(bottomConstraint) + } + case .left: + if let leadingConstraint { + removeConstraint(leadingConstraint) + } + case .right: + if let trailingConstraint { + removeConstraint(trailingConstraint) + } + case .top: + if let topConstraint { + removeConstraint(topConstraint) + } + case .bottom: + if let bottomConstraint { + removeConstraint(bottomConstraint) + } + default: + break + } + } + } +} + +//-------------------------------------------------- +// MARK: - Pinning +//-------------------------------------------------- +extension UIView { + public func pin(_ view: UIView, with edges: UIEdgeInsets = UIEdgeInsets.zero) { + leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: edges.left, identifier: ConstraintIdentifier.leading.description).isActive = true + trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -edges.right, identifier: ConstraintIdentifier.trailing.description).isActive = true + topAnchor.constraint(equalTo: view.topAnchor, constant: edges.top, identifier: ConstraintIdentifier.top.description).isActive = true + bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -edges.bottom, identifier: ConstraintIdentifier.bottom.description).isActive = true + } + public func pinToSuperView(_ edges: UIEdgeInsets = UIEdgeInsets.zero) { if let superview { pin(superview, with: edges) } } +} + +//-------------------------------------------------- +// MARK: - HeightAnchor +//-------------------------------------------------- +extension UIView { + + @discardableResult + public func height(_ constant: CGFloat, type: ConstraintType = .equal) -> Self { + switch type { + case .equal: + height(constant) + + case .lessThanOrEqual: + heightLessThanEqualTo(constant) + + case .greaterThanOrEqual: + heightGreaterThanEqualTo(constant) + } + return self + } @discardableResult public func height(_ constant: CGFloat) -> Self { - heightAnchor.constraint(equalToConstant: constant).isActive = true + heightAnchor.constraint(equalToConstant: constant, identifier: ConstraintIdentifier.height.description).isActive = true return self } - - @discardableResult - public func heightGreaterThanEqual(_ constant: CGFloat) -> Self { - heightAnchor.constraint(greaterThanOrEqualToConstant: constant).isActive = true - return self - } - - @discardableResult - public func heightLessThanEqual(_ constant: CGFloat) -> Self { - heightAnchor.constraint(lessThanOrEqualToConstant: constant).isActive = true - return self - } - - @discardableResult - public func width(_ constant: CGFloat) -> Self { - widthAnchor.constraint(equalToConstant: constant).isActive = true - return self - } - @discardableResult - public func widthGreaterThanEqual(_ constant: CGFloat) -> Self { - widthAnchor.constraint(greaterThanOrEqualToConstant: constant).isActive = true + public func heightGreaterThanEqualTo(_ constant: CGFloat) -> Self { + heightAnchor.constraint(greaterThanOrEqualToConstant: constant, identifier: ConstraintIdentifier.height.description).isActive = true return self } - + @discardableResult - public func widthLessThanEqual(_ constant: CGFloat) -> Self { - widthAnchor.constraint(lessThanOrEqualToConstant: constant).isActive = true + public func heightLessThanEqualTo(_ constant: CGFloat) -> Self { + heightAnchor.constraint(lessThanOrEqualToConstant: constant, identifier: ConstraintIdentifier.height.description).isActive = true return self } + +} +//-------------------------------------------------- +// MARK: - WidthAnchor +//-------------------------------------------------- +extension UIView { + + @discardableResult + public func width(_ constant: CGFloat, type: ConstraintType = .equal) -> Self { + switch type { + case .equal: + width(constant) + + case .lessThanOrEqual: + widthLessThanEqualTo(constant) + + case .greaterThanOrEqual: + widthGreaterThanEqualTo(constant) + } + return self + } + + @discardableResult + public func width(_ constant: CGFloat) -> Self { + widthAnchor.constraint(equalToConstant: constant, identifier: ConstraintIdentifier.width.description).isActive = true + return self + } + + @discardableResult + public func widthGreaterThanEqualTo(_ constant: CGFloat) -> Self { + widthAnchor.constraint(greaterThanOrEqualToConstant: constant, identifier: ConstraintIdentifier.width.description).isActive = true + return self + } + + @discardableResult + public func widthLessThanEqualTo(_ constant: CGFloat) -> Self { + widthAnchor.constraint(lessThanOrEqualToConstant: constant, identifier: ConstraintIdentifier.width.description).isActive = true + return self + } +} + +//-------------------------------------------------- +// MARK: - TopAnchor +//-------------------------------------------------- +extension UIView { + + @discardableResult + public func pinTop(_ anchor: NSLayoutYAxisAnchor? = nil, _ constant: CGFloat = 0.0, _ type: ConstraintType = .equal) -> Self { + switch type { + case .equal: + pinTop(anchor, constant) + + case .lessThanOrEqual: + pinTopLessThanOrEqualTo(anchor, constant) + + case .greaterThanOrEqual: + pinTopGreaterThanOrEqualTo(anchor, constant) + } + return self + } + @discardableResult public func pinTop(_ constant: CGFloat = 0.0) -> Self { return pinTop(nil, constant) @@ -69,11 +216,49 @@ extension UIView { public func pinTop(_ anchor: NSLayoutYAxisAnchor? = nil, _ constant: CGFloat = 0.0) -> Self { let found: NSLayoutYAxisAnchor? = anchor ?? superview?.topAnchor if let found { - topAnchor.constraint(equalTo: found, constant: constant).isActive = true + topAnchor.constraint(equalTo: found, constant: constant, identifier: ConstraintIdentifier.top.description).isActive = true } return self } + + @discardableResult + public func pinTopLessThanOrEqualTo(_ anchor: NSLayoutYAxisAnchor? = nil, _ constant: CGFloat = 0.0) -> Self { + let found: NSLayoutYAxisAnchor? = anchor ?? superview?.topAnchor + if let found { + topAnchor.constraint(lessThanOrEqualTo: found, constant: constant, identifier: ConstraintIdentifier.top.description).isActive = true + } + return self + } + + @discardableResult + public func pinTopGreaterThanOrEqualTo(_ anchor: NSLayoutYAxisAnchor? = nil, _ constant: CGFloat = 0.0) -> Self { + let found: NSLayoutYAxisAnchor? = anchor ?? superview?.topAnchor + if let found { + topAnchor.constraint(greaterThanOrEqualTo: found, constant: constant, identifier: ConstraintIdentifier.top.description).isActive = true + } + return self + } +} +//-------------------------------------------------- +// MARK: - BottomAnchor +//-------------------------------------------------- +extension UIView { + @discardableResult + public func pinBottom(_ anchor: NSLayoutYAxisAnchor? = nil, _ constant: CGFloat = 0.0, _ type: ConstraintType = .equal) -> Self { + switch type { + case .equal: + pinBottom(anchor, constant) + + case .lessThanOrEqual: + pinBottomLessThanOrEqualTo(anchor, constant) + + case .greaterThanOrEqual: + pinBottomGreaterThanOrEqualTo(anchor, constant) + } + return self + } + @discardableResult public func pinBottom(_ constant: CGFloat = 0.0) -> Self { return pinBottom(nil, constant) @@ -83,7 +268,45 @@ extension UIView { public func pinBottom(_ anchor: NSLayoutYAxisAnchor? = nil, _ constant: CGFloat = 0.0) -> Self { let found: NSLayoutYAxisAnchor? = anchor ?? superview?.bottomAnchor if let found { - bottomAnchor.constraint(equalTo: found, constant: -constant).isActive = true + bottomAnchor.constraint(equalTo: found, constant: -constant, identifier: ConstraintIdentifier.bottom.description).isActive = true + } + return self + } + + @discardableResult + public func pinBottomLessThanOrEqualTo(_ anchor: NSLayoutYAxisAnchor? = nil, _ constant: CGFloat = 0.0) -> Self { + let found: NSLayoutYAxisAnchor? = anchor ?? superview?.bottomAnchor + if let found { + bottomAnchor.constraint(lessThanOrEqualTo: found, constant: -constant, identifier: ConstraintIdentifier.bottom.description).isActive = true + } + return self + } + + @discardableResult + public func pinBottomGreaterThanOrEqualTo(_ anchor: NSLayoutYAxisAnchor? = nil, _ constant: CGFloat = 0.0) -> Self { + let found: NSLayoutYAxisAnchor? = anchor ?? superview?.bottomAnchor + if let found { + bottomAnchor.constraint(greaterThanOrEqualTo: found, constant: -constant, identifier: ConstraintIdentifier.bottom.description).isActive = true + } + return self + } +} + +//-------------------------------------------------- +// MARK: - LeadingAnchor +//-------------------------------------------------- +extension UIView { + @discardableResult + public func pinLeading(_ anchor: NSLayoutXAxisAnchor? = nil, _ constant: CGFloat = 0.0, _ type: ConstraintType = .equal) -> Self { + switch type { + case .equal: + pinLeading(anchor, constant) + + case .lessThanOrEqual: + pinLeadingLessThanOrEqualTo(anchor, constant) + + case .greaterThanOrEqual: + pinLeadingGreaterThanOrEqualTo(anchor, constant) } return self } @@ -92,16 +315,55 @@ extension UIView { public func pinLeading(_ constant: CGFloat = 0.0) -> Self { return pinLeading(nil, constant) } - + @discardableResult public func pinLeading(_ anchor: NSLayoutXAxisAnchor? = nil, _ constant: CGFloat = 0.0) -> Self { let found: NSLayoutXAxisAnchor? = anchor ?? superview?.leadingAnchor if let found { - leadingAnchor.constraint(equalTo: found, constant: constant).isActive = true + leadingAnchor.constraint(equalTo: found, constant: constant, identifier: ConstraintIdentifier.leading.description).isActive = true } return self } + + @discardableResult + public func pinLeadingLessThanOrEqualTo(_ anchor: NSLayoutXAxisAnchor? = nil, _ constant: CGFloat = 0.0) -> Self { + let found: NSLayoutXAxisAnchor? = anchor ?? superview?.leadingAnchor + if let found { + leadingAnchor.constraint(lessThanOrEqualTo: found, constant: constant, identifier: ConstraintIdentifier.leading.description).isActive = true + } + return self + } + + @discardableResult + public func pinLeadingGreaterThanOrEqualTo(_ anchor: NSLayoutXAxisAnchor? = nil, _ constant: CGFloat = 0.0) -> Self { + let found: NSLayoutXAxisAnchor? = anchor ?? superview?.leadingAnchor + if let found { + leadingAnchor.constraint(greaterThanOrEqualTo: found, constant: constant, identifier: ConstraintIdentifier.leading.description).isActive = true + } + return self + } +} +//-------------------------------------------------- +// MARK: - TrailingAnchor +//-------------------------------------------------- +extension UIView { + + @discardableResult + public func pinTrailing(_ anchor: NSLayoutXAxisAnchor? = nil, _ constant: CGFloat = 0.0, _ type: ConstraintType = .equal) -> Self { + switch type { + case .equal: + pinTrailing(anchor, constant) + + case .lessThanOrEqual: + pinTrailingLessThanOrEqualTo(anchor, constant) + + case .greaterThanOrEqual: + pinLeadingGreaterThanOrEqualTo(anchor, constant) + } + return self + } + @discardableResult public func pinTrailing(_ constant: CGFloat = 0.0) -> Self { return pinTrailing(nil, constant) @@ -111,13 +373,33 @@ extension UIView { public func pinTrailing(_ anchor: NSLayoutXAxisAnchor? = nil, _ constant: CGFloat = 0.0) -> Self { let found: NSLayoutXAxisAnchor? = anchor ?? superview?.trailingAnchor if let found { - trailingAnchor.constraint(equalTo: found, constant: -constant).isActive = true + trailingAnchor.constraint(equalTo: found, constant: -constant, identifier: ConstraintIdentifier.trailing.description).isActive = true + } + return self + } + + @discardableResult + public func pinTrailingLessThanOrEqualTo(_ anchor: NSLayoutXAxisAnchor? = nil, _ constant: CGFloat = 0.0) -> Self { + let found: NSLayoutXAxisAnchor? = anchor ?? superview?.trailingAnchor + if let found { + trailingAnchor.constraint(lessThanOrEqualTo: found, constant: -constant, identifier: ConstraintIdentifier.trailing.description).isActive = true } return self } + @discardableResult + public func pinTrailingGreaterThanOrEqualTo(_ anchor: NSLayoutXAxisAnchor? = nil, _ constant: CGFloat = 0.0) -> Self { + let found: NSLayoutXAxisAnchor? = anchor ?? superview?.trailingAnchor + if let found { + trailingAnchor.constraint(greaterThanOrEqualTo: found, constant: -constant, identifier: ConstraintIdentifier.trailing.description).isActive = true + } + return self + } } +//-------------------------------------------------- +// MARK: - Debug Borders +//-------------------------------------------------- extension UIView { internal func removeDebugBorder() { @@ -174,6 +456,9 @@ extension UIView { } } +//-------------------------------------------------- +// MARK: - CALayer +//-------------------------------------------------- extension CALayer { func remove(layerName: String) { guard let sublayers = sublayers else { @@ -188,7 +473,9 @@ extension CALayer { } } - +//-------------------------------------------------- +// MARK: - Borders +//-------------------------------------------------- extension UIView { public func addBorder(side: UIRectEdge, width: CGFloat, color: UIColor, offset: CGFloat = 0) { @@ -239,3 +526,126 @@ extension UIView { } } } + +//-------------------------------------------------- +// MARK: - NSLayoutAnchor +//-------------------------------------------------- +extension NSLayoutAnchor { + // These methods return an inactive constraint of the form thisAnchor = otherAnchor. + @discardableResult + @objc public func constraint(equalTo anchor: NSLayoutAnchor, identifier: String) -> NSLayoutConstraint { + let constraint = self.constraint(equalTo: anchor) + constraint.identifier = identifier + return constraint + } + + @discardableResult + @objc public func constraint(greaterThanOrEqualTo anchor: NSLayoutAnchor, identifier: String) -> NSLayoutConstraint { + let constraint = self.constraint(greaterThanOrEqualTo: anchor) + constraint.identifier = identifier + return constraint + } + + @discardableResult + @objc public func constraint(lessThanOrEqualTo anchor: NSLayoutAnchor, identifier: String) -> NSLayoutConstraint { + let constraint = self.constraint(lessThanOrEqualTo: anchor) + constraint.identifier = identifier + return constraint + } + + + @discardableResult + @objc public func constraint(equalTo anchor: NSLayoutAnchor, constant: CGFloat, identifier: String) -> NSLayoutConstraint { + let constraint = self.constraint(equalTo: anchor, constant: constant) + constraint.identifier = identifier + return constraint + } + + @discardableResult + @objc public func constraint(greaterThanOrEqualTo anchor: NSLayoutAnchor, constant: CGFloat, identifier: String) -> NSLayoutConstraint { + let constraint = self.constraint(greaterThanOrEqualTo: anchor, constant: constant) + constraint.identifier = identifier + return constraint + } + + @discardableResult + @objc public func constraint(lessThanOrEqualTo anchor: NSLayoutAnchor, constant: CGFloat, identifier: String) -> NSLayoutConstraint { + let constraint = self.constraint(lessThanOrEqualTo: anchor, constant: constant) + constraint.identifier = identifier + return constraint + } +} + +//-------------------------------------------------- +// MARK: - NSLayoutDimension +//-------------------------------------------------- +extension NSLayoutDimension { + // These methods return an inactive constraint of the form thisVariable = constant. + @discardableResult + @objc public func constraint(equalToConstant c: CGFloat, identifier: String) -> NSLayoutConstraint { + let lc = constraint(equalToConstant: c) + lc.identifier = identifier + return lc + } + + @discardableResult + @objc public func constraint(greaterThanOrEqualToConstant c: CGFloat, identifier: String) -> NSLayoutConstraint { + let lc = constraint(greaterThanOrEqualToConstant: c) + lc.identifier = identifier + return lc + } + + @discardableResult + @objc public func constraint(lessThanOrEqualToConstant c: CGFloat, identifier: String) -> NSLayoutConstraint { + let lc = constraint(lessThanOrEqualToConstant: c) + lc.identifier = identifier + return lc + } + + + // These methods return an inactive constraint of the form thisAnchor = otherAnchor * multiplier. + @discardableResult + @objc public func constraint(equalTo anchor: NSLayoutDimension, multiplier m: CGFloat, identifier: String) -> NSLayoutConstraint { + let lc = constraint(equalTo: anchor, multiplier: m) + lc.identifier = identifier + return lc + } + + @discardableResult + @objc public func constraint(greaterThanOrEqualTo anchor: NSLayoutDimension, multiplier m: CGFloat, identifier: String) -> NSLayoutConstraint { + let lc = constraint(greaterThanOrEqualTo: anchor ,multiplier: m) + lc.identifier = identifier + return lc + } + + @discardableResult + @objc public func constraint(lessThanOrEqualTo anchor: NSLayoutDimension, multiplier m: CGFloat, identifier: String) -> NSLayoutConstraint { + let lc = constraint(lessThanOrEqualTo: anchor, multiplier: m) + lc.identifier = identifier + return lc + } + + + // These methods return an inactive constraint of the form thisAnchor = otherAnchor * multiplier + constant. + @discardableResult + @objc public func constraint(equalTo anchor: NSLayoutDimension, multiplier m: CGFloat, constant c: CGFloat, identifier: String) -> NSLayoutConstraint { + let lc = constraint(equalTo: anchor, multiplier: m, constant: c) + lc.identifier = identifier + return lc + } + + @discardableResult + @objc public func constraint(greaterThanOrEqualTo anchor: NSLayoutDimension, multiplier m: CGFloat, constant c: CGFloat, identifier: String) -> NSLayoutConstraint { + let lc = constraint(greaterThanOrEqualTo: anchor, multiplier: m, constant: c) + lc.identifier = identifier + return lc + } + + @discardableResult + @objc public func constraint(lessThanOrEqualTo anchor: NSLayoutDimension, multiplier m: CGFloat, constant c: CGFloat, identifier: String) -> NSLayoutConstraint { + let lc = constraint(lessThanOrEqualTo: anchor, multiplier: m, constant: c) + lc.identifier = identifier + return lc + } + +}