// // LayoutConstraintable.swift // VDS // // Created by Matt Bruce on 8/22/23. // import Foundation import UIKit import VDSFormControlsTokens public protocol LayoutConstraintable { var superview: UIView? { get } var leadingAnchor: NSLayoutXAxisAnchor { get } var trailingAnchor: NSLayoutXAxisAnchor { get } var topAnchor: NSLayoutYAxisAnchor { get } var bottomAnchor: NSLayoutYAxisAnchor { get } var widthAnchor: NSLayoutDimension { get } var heightAnchor: NSLayoutDimension { get } var centerXAnchor: NSLayoutXAxisAnchor { get } var centerYAnchor: NSLayoutYAxisAnchor { get } } //-------------------------------------------------- // MARK: - Pinning //-------------------------------------------------- extension LayoutConstraintable { @discardableResult /// Pins each to the all 4 anchor points to a view. /// - Parameters: /// - layoutConstrainable: LayoutConstrainable that you will be pinned within. /// - edges: Insets for each side. /// - Returns: Yourself. public func pin(_ layoutConstrainable: LayoutConstraintable, with edges: UIEdgeInsets = UIEdgeInsets.zero) -> Self { pinLeading(layoutConstrainable.leadingAnchor, edges.left) pinTrailing(layoutConstrainable.trailingAnchor, edges.right) pinTop(layoutConstrainable.topAnchor, edges.top) pinBottom(layoutConstrainable.bottomAnchor, edges.bottom) return self } @discardableResult /// Pins each to the all 4 anchor points to the view you are set within. /// - Parameter edges: Insets for each side. /// - Returns: Yourself. public func pinToSuperView(_ edges: UIEdgeInsets = UIEdgeInsets.zero) -> Self { if let superview { pin(superview, with: edges) } return self } } //-------------------------------------------------- // MARK: - HeightAnchor //-------------------------------------------------- extension LayoutConstraintable { @discardableResult /// Adds a heightAnchor. /// - Parameter constant: Constant size. /// - Returns: Yourself. public func height(_ constant: CGFloat, _ priority: UILayoutPriority = .required) -> Self { height(constant: constant, priority: priority) return self } @discardableResult /// Adds a heightAnchor where the height constant passed in using a greaterThanOrEqualTo Constraint. /// - Parameter constant: Constant size. /// - Returns: Yourself. public func heightGreaterThanEqualTo(_ constant: CGFloat, _ priority: UILayoutPriority = .required) -> Self { heightGreaterThanEqualTo(constant: constant, priority: priority) return self } @discardableResult /// Adds a heightAnchor where the height constant passed in using a lessThanOrEqualTo Constraint. /// - Parameter constant: Constant size. /// - Returns: Yourself. public func heightLessThanEqualTo(_ constant: CGFloat, _ priority: UILayoutPriority = .required) -> Self { heightLessThanEqualTo(constant: constant, priority: priority) return self } @discardableResult /// Adds a heightAnchor for the constant passed into the method. /// - Parameter constant: Constant size. /// - Returns: The Constraint that was created. public func height(constant: CGFloat, priority: UILayoutPriority = .required) -> NSLayoutConstraint { heightAnchor.constraint(equalToConstant: constant).with { $0.priority = priority; $0.isActive = true } } @discardableResult /// Adds a heightAnchor where the constant passed in using a greaterThanOrEqualTo Constraint. /// - Parameter constant: Constant size. /// - Returns: The Constraint that was created. public func heightGreaterThanEqualTo(constant: CGFloat, priority: UILayoutPriority = .required) -> NSLayoutConstraint { heightAnchor.constraint(greaterThanOrEqualToConstant: constant).with { $0.priority = priority; $0.isActive = true } } @discardableResult /// Adds a heightAnchor where the constant passed in using a lessThanOrEqualTo Constraint. /// - Parameter constant: Constant size. /// - Returns: The Constraint that was created. public func heightLessThanEqualTo(constant: CGFloat, priority: UILayoutPriority = .required) -> NSLayoutConstraint { heightAnchor.constraint(lessThanOrEqualToConstant: constant).with { $0.priority = priority; $0.isActive = true } } } //-------------------------------------------------- // MARK: - WidthAnchor //-------------------------------------------------- extension LayoutConstraintable { @discardableResult /// Adds a widthAnchor. /// - Parameter constant: Width Constant size. /// - Returns: Yourself. public func width(_ constant: CGFloat, _ priority: UILayoutPriority = .required) -> Self { width(constant: constant, priority: priority) return self } @discardableResult /// Adds a widthAnchor where the constant passed in using a greaterThanOrEqualTo Constraint. /// - Parameter constant: Constant size. /// - Returns: Yourself. public func widthGreaterThanEqualTo(_ constant: CGFloat, _ priority: UILayoutPriority = .required) -> Self { widthGreaterThanEqualTo(constant: constant, priority: priority) return self } @discardableResult /// Adds a widthAnchor where the constant passed in using a lessThanOrEqualTo Constraint. /// - Parameter constant: Constant size. /// - Returns: Yourself. public func widthLessThanEqualTo(_ constant: CGFloat, _ priority: UILayoutPriority = .required) -> Self { widthLessThanEqualTo(constant: constant, priority: priority) return self } @discardableResult /// Adds a widthAnchor for the constant passed into the method. /// - Parameter constant: Constant size. /// - Returns: The Constraint that was created. public func width(constant: CGFloat, priority: UILayoutPriority = .required) -> NSLayoutConstraint { widthAnchor.constraint(equalToConstant: constant).with { $0.priority = priority; $0.isActive = true } } @discardableResult /// Adds a widthAnchor with the constant passed in using a greaterThanOrEqualTo Constraint. /// - Parameter constant: Constant size. /// - Returns: The Constraint that was created. public func widthGreaterThanEqualTo(constant: CGFloat, priority: UILayoutPriority = .required) -> NSLayoutConstraint { widthAnchor.constraint(greaterThanOrEqualToConstant: constant).with { $0.priority = priority; $0.isActive = true } } @discardableResult /// Adds a widthAnchor with the constant passed in using a lessThanOrEqualTo Constraint. /// - Parameter constant: Constant size. /// - Returns: The Constraint that was created. public func widthLessThanEqualTo(constant: CGFloat, priority: UILayoutPriority = .required) -> NSLayoutConstraint { widthAnchor.constraint(lessThanOrEqualToConstant: constant).with { $0.priority = priority; $0.isActive = true } } } //-------------------------------------------------- // MARK: - TopAnchor //-------------------------------------------------- extension LayoutConstraintable { @discardableResult /// Adds a topAnchor. /// - Parameter constant: Constant size. /// - Returns: Yourself. public func pinTop(_ constant: CGFloat = 0.0, _ priority: UILayoutPriority = .required) -> Self { return pinTop(nil, constant, priority) } @discardableResult /// Adds a topAnchor to a specific YAxisAnchor. /// - Parameter anchor:The anchor in which to attach the topAnchor /// - constant: Constant size. /// - Returns: Yourself. public func pinTop(_ anchor: NSLayoutYAxisAnchor? = nil, _ constant: CGFloat = 0.0, _ priority: UILayoutPriority = .required) -> Self { pinTop(anchor: anchor, constant: constant, priority: priority) return self } @discardableResult /// Adds a topAnchor to a specific YAxisAnchor passed in using a lessThanOrEqualTo Constraint /// - Parameter anchor:The anchor in which to attach the topAnchor /// - constant: Constant size. /// - Returns: Yourself. public func pinTopLessThanOrEqualTo(_ anchor: NSLayoutYAxisAnchor? = nil, _ constant: CGFloat = 0.0, _ priority: UILayoutPriority = .required) -> Self { pinTopLessThanOrEqualTo(anchor: anchor, constant: constant, priority: priority) return self } @discardableResult /// Adds a topAnchor to a specific YAxisAnchor passed in using a greaterThanOrEqualTo Constraint /// - Parameter anchor:The anchor in which to attach the topAnchor /// - constant: Constant size. /// - Returns: Yourself. public func pinTopGreaterThanOrEqualTo(_ anchor: NSLayoutYAxisAnchor? = nil, _ constant: CGFloat = 0.0, _ priority: UILayoutPriority = .required) -> Self { pinTopGreaterThanOrEqualTo(anchor: anchor, constant: constant, priority: priority) return self } @discardableResult /// Adds a topAnchor for the constant passed into the method. /// - Parameter anchor:The anchor in which to attach the topAnchor /// - constant: Constant size. /// - Returns: The Constraint that was created. public func pinTop(anchor: NSLayoutYAxisAnchor?, constant: CGFloat = 0.0, priority: UILayoutPriority = .required) -> NSLayoutConstraint? { let found: NSLayoutYAxisAnchor? = anchor ?? superview?.topAnchor guard let found else { return nil } return topAnchor.constraint(equalTo: found, constant: constant).with { $0.priority = priority; $0.isActive = true } } @discardableResult /// Adds a topAnchor with the constant passed in using a lessThanOrEqualTo Constraint. /// - Parameter anchor:The anchor in which to attach the topAnchor /// - constant: Constant size. /// - Returns: The Constraint that was created. public func pinTopLessThanOrEqualTo(anchor: NSLayoutYAxisAnchor?, constant: CGFloat = 0.0, priority: UILayoutPriority = .required) -> NSLayoutConstraint? { let found: NSLayoutYAxisAnchor? = anchor ?? superview?.topAnchor guard let found else { return nil } return topAnchor.constraint(lessThanOrEqualTo: found, constant: constant).with { $0.priority = priority; $0.isActive = true } } @discardableResult /// Adds a topAnchor with the constant passed in using a greaterThanOrEqualTo Constraint. /// - Parameter anchor:The anchor in which to attach the topAnchor /// - constant: Constant size. /// - Returns: The Constraint that was created. public func pinTopGreaterThanOrEqualTo(anchor: NSLayoutYAxisAnchor?, constant: CGFloat = 0.0, priority: UILayoutPriority = .required) -> NSLayoutConstraint? { let found: NSLayoutYAxisAnchor? = anchor ?? superview?.topAnchor guard let found else { return nil } return topAnchor.constraint(greaterThanOrEqualTo: found, constant: constant).with { $0.priority = priority; $0.isActive = true } } } //-------------------------------------------------- // MARK: - BottomAnchor //-------------------------------------------------- extension LayoutConstraintable { @discardableResult /// Adds a bottomAnchor. /// - Parameter constant: Constant size. /// - Returns: Yourself. public func pinBottom(_ constant: CGFloat = 0.0, _ priority: UILayoutPriority = .required) -> Self { return pinBottom(nil, constant, priority) } @discardableResult /// Adds a bottomAnchor to a specific YAxisAnchor. /// - Parameter anchor:The anchor in which to attach the bottomAnchor /// - constant: Constant size. /// - Returns: Yourself. public func pinBottom(_ anchor: NSLayoutYAxisAnchor? = nil, _ constant: CGFloat = 0.0, _ priority: UILayoutPriority = .required) -> Self { pinBottom(anchor: anchor, constant: constant, priority: priority) return self } @discardableResult /// Adds a bottomAnchor to a specific YAxisAnchor passed in using a lessThanOrEqualTo Constraint /// - Parameter anchor:The anchor in which to attach the bottomAnchor /// - constant: Constant size. /// - Returns: Yourself. public func pinBottomLessThanOrEqualTo(_ anchor: NSLayoutYAxisAnchor? = nil, _ constant: CGFloat = 0.0, _ priority: UILayoutPriority = .required) -> Self { pinBottomLessThanOrEqualTo(anchor: anchor, constant: constant, priority: priority) return self } @discardableResult /// Adds a bottomAnchor to a specific YAxisAnchor passed in using a greaterThanOrEqualTo Constraint /// - Parameter anchor:The anchor in which to attach the bottomAnchor /// - constant: Constant size. /// - Returns: Yourself. public func pinBottomGreaterThanOrEqualTo(_ anchor: NSLayoutYAxisAnchor? = nil, _ constant: CGFloat = 0.0, _ priority: UILayoutPriority = .required) -> Self { pinBottomGreaterThanOrEqualTo(anchor: anchor, constant: constant, priority: priority) return self } @discardableResult /// Adds a bottomAnchor for the constant passed into the method. /// - Parameter anchor:The anchor in which to attach the bottomAnchor /// - constant: Constant size. /// - Returns: The Constraint that was created. public func pinBottom(anchor: NSLayoutYAxisAnchor?, constant: CGFloat = 0.0, priority: UILayoutPriority = .required) -> NSLayoutConstraint? { let found: NSLayoutYAxisAnchor? = anchor ?? superview?.bottomAnchor guard let found else { return nil } return bottomAnchor.constraint(equalTo: found, constant: -constant).with { $0.priority = priority; $0.isActive = true } } @discardableResult /// Adds a bottomAnchor with the constant passed in using a lessThanOrEqualTo Constraint. /// - Parameter anchor:The anchor in which to attach the bottomAnchor /// - constant: Constant size. /// - Returns: The Constraint that was created. public func pinBottomLessThanOrEqualTo(anchor: NSLayoutYAxisAnchor?, constant: CGFloat = 0.0, priority: UILayoutPriority = .required) -> NSLayoutConstraint? { let found: NSLayoutYAxisAnchor? = anchor ?? superview?.bottomAnchor guard let found else { return nil } return bottomAnchor.constraint(lessThanOrEqualTo: found, constant: -constant).with { $0.priority = priority; $0.isActive = true } } @discardableResult /// Adds a bottomAnchor with the constant passed in using a greaterThanOrEqualTo Constraint. /// - Parameter anchor:The anchor in which to attach the bottomAnchor /// - constant: Constant size. /// - Returns: The Constraint that was created. public func pinBottomGreaterThanOrEqualTo(anchor: NSLayoutYAxisAnchor?, constant: CGFloat = 0.0, priority: UILayoutPriority = .required) -> NSLayoutConstraint? { let found: NSLayoutYAxisAnchor? = anchor ?? superview?.bottomAnchor guard let found else { return nil } return bottomAnchor.constraint(greaterThanOrEqualTo: found, constant: -constant).with { $0.priority = priority; $0.isActive = true } } } //-------------------------------------------------- // MARK: - LeadingAnchor //-------------------------------------------------- extension LayoutConstraintable { @discardableResult /// Adds a leadingAnchor. /// - Parameter constant: Constant size. /// - Returns: Yourself. public func pinLeading(_ constant: CGFloat = 0.0, _ priority: UILayoutPriority = .required) -> Self { return pinLeading(nil, constant, priority) } @discardableResult /// Adds a leadingAnchor to a specific XAxisAnchor. /// - Parameter anchor:The anchor in which to attach the leadingAnchor. /// - constant: Constant size. /// - Returns: Yourself. public func pinLeading(_ anchor: NSLayoutXAxisAnchor? = nil, _ constant: CGFloat = 0.0, _ priority: UILayoutPriority = .required) -> Self { pinLeading(anchor: anchor, constant: constant, priority: priority) return self } @discardableResult /// Adds a leadingAnchor to a specific XAxisAnchor passed in using a greaterThanOrEqualTo Constraint /// - Parameter anchor:The anchor in which to attach the leadingAnchor /// - constant: Constant size. /// - Returns: Yourself. public func pinLeadingLessThanOrEqualTo(_ anchor: NSLayoutXAxisAnchor? = nil, _ constant: CGFloat = 0.0, _ priority: UILayoutPriority = .required) -> Self { pinLeadingLessThanOrEqualTo(anchor: anchor, constant: constant, priority: priority) return self } @discardableResult /// Adds a leadingAnchor to a specific XAxisAnchor passed in using a greaterThanOrEqualTo Constraint /// - Parameter anchor:The anchor in which to attach the leadingAnchor /// - constant: Constant size. /// - Returns: Yourself. public func pinLeadingGreaterThanOrEqualTo(_ anchor: NSLayoutXAxisAnchor? = nil, _ constant: CGFloat = 0.0, _ priority: UILayoutPriority = .required) -> Self { pinLeadingGreaterThanOrEqualTo(anchor: anchor, constant: constant, priority: priority) return self } @discardableResult /// Adds a leadingAnchor for the constant passed into the method. /// - Parameter anchor:The anchor in which to attach the leadingAnchor /// - constant: Constant size. /// - Returns: The Constraint that was created. public func pinLeading(anchor: NSLayoutXAxisAnchor?, constant: CGFloat = 0.0, priority: UILayoutPriority = .required) -> NSLayoutConstraint? { let found: NSLayoutXAxisAnchor? = anchor ?? superview?.leadingAnchor guard let found else { return nil } return leadingAnchor.constraint(equalTo: found, constant: constant).with { $0.priority = priority; $0.isActive = true } } @discardableResult /// Adds a leadingAnchor with the constant passed in using a lessThanOrEqualTo Constraint. /// - Parameter anchor:The anchor in which to attach the leadingAnchor /// - constant: Constant size. /// - Returns: The Constraint that was created. public func pinLeadingLessThanOrEqualTo(anchor: NSLayoutXAxisAnchor?, constant: CGFloat = 0.0, priority: UILayoutPriority = .required) -> NSLayoutConstraint? { let found: NSLayoutXAxisAnchor? = anchor ?? superview?.leadingAnchor guard let found else { return nil } return leadingAnchor.constraint(lessThanOrEqualTo: found, constant: constant).with { $0.priority = priority; $0.isActive = true } } @discardableResult /// Adds a leadingAnchor with the constant passed in using a greaterThanOrEqualTo Constraint. /// - Parameter anchor:The anchor in which to attach the leadingAnchor /// - constant: Constant size. /// - Returns: The Constraint that was created. public func pinLeadingGreaterThanOrEqualTo(anchor: NSLayoutXAxisAnchor?, constant: CGFloat = 0.0, priority: UILayoutPriority = .required) -> NSLayoutConstraint? { let found: NSLayoutXAxisAnchor? = anchor ?? superview?.leadingAnchor guard let found else { return nil } return leadingAnchor.constraint(greaterThanOrEqualTo: found, constant: constant).with { $0.priority = priority; $0.isActive = true } } } //-------------------------------------------------- // MARK: - TrailingAnchor //-------------------------------------------------- extension LayoutConstraintable { @discardableResult /// Adds a trailingAnchor. /// - Parameter constant: Constant size. /// - Returns: Yourself. public func pinTrailing(_ constant: CGFloat = 0.0, _ priority: UILayoutPriority = .required) -> Self { pinTrailing(nil, constant) } @discardableResult /// Adds a trailingAnchor to a specific XAxisAnchor. /// - Parameter anchor:The anchor in which to attach the trailingAnchor. /// - constant: Constant size. /// - Returns: Yourself. public func pinTrailing(_ anchor: NSLayoutXAxisAnchor? = nil, _ constant: CGFloat = 0.0, _ priority: UILayoutPriority = .required) -> Self { pinTrailing(anchor: anchor, constant: constant) return self } @discardableResult /// Adds a trailingAnchor to a specific XAxisAnchor passed in using a lessThanOrEqualTo Constraint /// - Parameter anchor:The anchor in which to attach the trailingAnchor /// - constant: Constant size. /// - Returns: Yourself. public func pinTrailingLessThanOrEqualTo(_ anchor: NSLayoutXAxisAnchor? = nil, _ constant: CGFloat = 0.0, _ priority: UILayoutPriority = .required) -> Self { pinTrailingLessThanOrEqualTo(anchor: anchor, constant: constant) return self } @discardableResult /// Adds a trailingAnchor to a specific XAxisAnchor passed in using a greaterThanOrEqualTo Constraint /// - Parameter anchor:The anchor in which to attach the trailingAnchor /// - constant: Constant size. /// - Returns: Yourself. public func pinTrailingGreaterThanOrEqualTo(_ anchor: NSLayoutXAxisAnchor? = nil, _ constant: CGFloat = 0.0) -> Self { pinTrailingGreaterThanOrEqualTo(anchor: anchor, constant: constant) return self } @discardableResult /// Adds a trailingAnchor for the constant passed into the method. /// - Parameter anchor:The anchor in which to attach the trailingAnchor /// - constant: Constant size. /// - Returns: The Constraint that was created. public func pinTrailing(anchor: NSLayoutXAxisAnchor?, constant: CGFloat = 0.0, priority: UILayoutPriority = .required) -> NSLayoutConstraint? { let found: NSLayoutXAxisAnchor? = anchor ?? superview?.trailingAnchor guard let found else { return nil } return trailingAnchor.constraint(equalTo: found, constant: -constant).with { $0.priority = priority; $0.isActive = true } } @discardableResult /// Adds a trailingAnchor with the constant passed in using a lessThanOrEqualTo Constraint. /// - Parameter anchor:The anchor in which to attach the trailingAnchor /// - constant: Constant size. /// - Returns: The Constraint that was created. public func pinTrailingLessThanOrEqualTo(anchor: NSLayoutXAxisAnchor?, constant: CGFloat = 0.0, priority: UILayoutPriority = .required) -> NSLayoutConstraint? { let found: NSLayoutXAxisAnchor? = anchor ?? superview?.trailingAnchor guard let found else { return nil } return trailingAnchor.constraint(lessThanOrEqualTo: found, constant: -constant).with { $0.priority = priority; $0.isActive = true } } @discardableResult /// Adds a trailingAnchor with the constant passed in using a greaterThanOrEqualTo Constraint. /// - Parameter anchor:The anchor in which to attach the trailingAnchor /// - constant: Constant size. /// - Returns: The Constraint that was created. public func pinTrailingGreaterThanOrEqualTo(anchor: NSLayoutXAxisAnchor?, constant: CGFloat = 0.0, priority: UILayoutPriority = .required) -> NSLayoutConstraint? { let found: NSLayoutXAxisAnchor? = anchor ?? superview?.trailingAnchor guard let found else { return nil } return trailingAnchor.constraint(greaterThanOrEqualTo: found, constant: -constant).with { $0.priority = priority; $0.isActive = true } } } //-------------------------------------------------- // MARK: - Implementations //-------------------------------------------------- extension UIView: LayoutConstraintable {} extension UILayoutGuide: LayoutConstraintable { public var superview: UIView? { owningView } }