// // LayoutConstraintable.swift // VDS // // Created by Matt Bruce on 8/22/23. // import Foundation import UIKit import VDSCoreTokens 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: - Center X Constraints //-------------------------------------------------- extension LayoutConstraintable { @discardableResult /// Adds a centerXAnchor. /// - Parameter constant: Constant size. /// - Returns: Yourself. public func pinCenterX(_ constant: CGFloat = 0.0, _ priority: UILayoutPriority = .required) -> Self { pinCenterX(nil, constant) } @discardableResult /// Adds a centerXAnchor to a specific XAxisAnchor. /// - Parameter anchor:The anchor in which to attach the centerXAnchor. /// - constant: Constant size. /// - Returns: Yourself. public func pinCenterX(_ anchor: NSLayoutXAxisAnchor? = nil, _ constant: CGFloat = 0.0, _ priority: UILayoutPriority = .required) -> Self { pinCenterX(anchor: anchor, constant: constant) return self } @discardableResult /// Adds a centerXAnchor to a specific XAxisAnchor passed in using a lessThanOrEqualTo Constraint /// - Parameter anchor:The anchor in which to attach the centerXAnchor /// - constant: Constant size. /// - Returns: Yourself. public func pinCenterXLessThanOrEqualTo(_ anchor: NSLayoutXAxisAnchor? = nil, _ constant: CGFloat = 0.0, _ priority: UILayoutPriority = .required) -> Self { pinCenterXLessThanOrEqualTo(anchor: anchor, constant: constant) return self } @discardableResult /// Adds a centerXAnchor to a specific XAxisAnchor passed in using a greaterThanOrEqualTo Constraint /// - Parameter anchor:The anchor in which to attach the centerXAnchor /// - constant: Constant size. /// - Returns: Yourself. public func pinCenterXGreaterThanOrEqualTo(_ anchor: NSLayoutXAxisAnchor? = nil, _ constant: CGFloat = 0.0) -> Self { pinCenterXGreaterThanOrEqualTo(anchor: anchor, constant: constant) return self } @discardableResult /// Adds a centerXAnchor for the constant passed into the method. /// - Parameter anchor:The anchor in which to attach the centerXAnchor /// - constant: Constant size. /// - Returns: The Constraint that was created. public func pinCenterX(anchor: NSLayoutXAxisAnchor?, constant: CGFloat = 0.0, priority: UILayoutPriority = .required) -> NSLayoutConstraint? { let found: NSLayoutXAxisAnchor? = anchor ?? superview?.centerXAnchor guard let found else { return nil } return centerXAnchor.constraint(equalTo: found, constant: -constant).with { $0.priority = priority; $0.isActive = true } } @discardableResult /// Adds a centerXAnchor with the constant passed in using a lessThanOrEqualTo Constraint. /// - Parameter anchor:The anchor in which to attach the centerXAnchor /// - constant: Constant size. /// - Returns: The Constraint that was created. public func pinCenterXLessThanOrEqualTo(anchor: NSLayoutXAxisAnchor?, constant: CGFloat = 0.0, priority: UILayoutPriority = .required) -> NSLayoutConstraint? { let found: NSLayoutXAxisAnchor? = anchor ?? superview?.centerXAnchor guard let found else { return nil } return centerXAnchor.constraint(lessThanOrEqualTo: found, constant: -constant).with { $0.priority = priority; $0.isActive = true } } @discardableResult /// Adds a centerXAnchor with the constant passed in using a greaterThanOrEqualTo Constraint. /// - Parameter anchor:The anchor in which to attach the centerXAnchor /// - constant: Constant size. /// - Returns: The Constraint that was created. public func pinCenterXGreaterThanOrEqualTo(anchor: NSLayoutXAxisAnchor?, constant: CGFloat = 0.0, priority: UILayoutPriority = .required) -> NSLayoutConstraint? { let found: NSLayoutXAxisAnchor? = anchor ?? superview?.centerXAnchor guard let found else { return nil } return centerXAnchor.constraint(greaterThanOrEqualTo: found, constant: -constant).with { $0.priority = priority; $0.isActive = true } } } //-------------------------------------------------- // MARK: - Center Y Constraints //-------------------------------------------------- extension LayoutConstraintable { @discardableResult /// Adds a centerYAnchor. /// - Parameter constant: Constant size. /// - Returns: Yourself. public func pinCenterY(_ constant: CGFloat = 0.0, _ priority: UILayoutPriority = .required) -> Self { pinCenterY(nil, constant) } @discardableResult /// Adds a centerYAnchor to a specific YAxisAnchor. /// - Parameter anchor:The anchor in which to attach the centerYAnchor. /// - constant: Constant size. /// - Returns: Yourself. public func pinCenterY(_ anchor: NSLayoutYAxisAnchor? = nil, _ constant: CGFloat = 0.0, _ priority: UILayoutPriority = .required) -> Self { pinCenterY(anchor: anchor, constant: constant) return self } @discardableResult /// Adds a centerYAnchor to a specific YAxisAnchor passed in using a lessThanOrEqualTo Constraint /// - Parameter anchor:The anchor in which to attach the centerYAnchor /// - constant: Constant size. /// - Returns: Yourself. public func pinCenterYLessThanOrEqualTo(_ anchor: NSLayoutYAxisAnchor? = nil, _ constant: CGFloat = 0.0, _ priority: UILayoutPriority = .required) -> Self { pinCenterYLessThanOrEqualTo(anchor: anchor, constant: constant) return self } @discardableResult /// Adds a centerYAnchor to a specific YAxisAnchor passed in using a greaterThanOrEqualTo Constraint /// - Parameter anchor:The anchor in which to attach the centerYAnchor /// - constant: Constant size. /// - Returns: Yourself. public func pinCenterYGreaterThanOrEqualTo(_ anchor: NSLayoutXAxisAnchor? = nil, _ constant: CGFloat = 0.0) -> Self { pinCenterXGreaterThanOrEqualTo(anchor: anchor, constant: constant) return self } @discardableResult /// Adds a centerYAnchor for the constant passed into the method. /// - Parameter anchor:The anchor in which to attach the centerYAnchor /// - constant: Constant size. /// - Returns: The Constraint that was created. public func pinCenterY(anchor: NSLayoutYAxisAnchor?, constant: CGFloat = 0.0, priority: UILayoutPriority = .required) -> NSLayoutConstraint? { let found: NSLayoutYAxisAnchor? = anchor ?? superview?.centerYAnchor guard let found else { return nil } return centerYAnchor.constraint(equalTo: found, constant: -constant).with { $0.priority = priority; $0.isActive = true } } @discardableResult /// Adds a centerYAnchor with the constant passed in using a lessThanOrEqualTo Constraint. /// - Parameter anchor:The anchor in which to attach the centerYAnchor /// - constant: Constant size. /// - Returns: The Constraint that was created. public func pinCenterYLessThanOrEqualTo(anchor: NSLayoutYAxisAnchor?, constant: CGFloat = 0.0, priority: UILayoutPriority = .required) -> NSLayoutConstraint? { let found: NSLayoutYAxisAnchor? = anchor ?? superview?.centerYAnchor guard let found else { return nil } return centerYAnchor.constraint(lessThanOrEqualTo: found, constant: -constant).with { $0.priority = priority; $0.isActive = true } } @discardableResult /// Adds a centerYAnchor with the constant passed in using a greaterThanOrEqualTo Constraint. /// - Parameter anchor:The anchor in which to attach the centerYAnchor /// - constant: Constant size. /// - Returns: The Constraint that was created. public func pinCenterYGreaterThanOrEqualTo(anchor: NSLayoutYAxisAnchor?, constant: CGFloat = 0.0, priority: UILayoutPriority = .required) -> NSLayoutConstraint? { let found: NSLayoutYAxisAnchor? = anchor ?? superview?.centerYAnchor guard let found else { return nil } return centerYAnchor.constraint(greaterThanOrEqualTo: found, constant: -constant).with { $0.priority = priority; $0.isActive = true } } } // alignment public enum LayoutAlignment: String, CaseIterable { case fill case leading case top case center case trailing case bottom } public enum LayoutDistribution: String, CaseIterable { case fill case fillProportionally } extension LayoutConstraintable where Self: UIView { public var constrainedWidth: CGFloat { horizontalPinnedWidth() ?? (superview?.frame.size.width ?? frame.size.width) } } extension LayoutConstraintable { public func removeConstraints() { guard let view = self as? UIView, let superview = view.superview else { return } // Remove all existing constraints on the containerView let superviewConstraints = superview.constraints for constraint in superviewConstraints { if constraint.firstItem as? UIView == view || constraint.secondItem as? UIView == view { superview.removeConstraint(constraint) } } } public func applyAlignment(_ alignment: LayoutAlignment, edges: UIEdgeInsets = UIEdgeInsets.zero) { guard let superview = superview else { return } removeConstraints() switch alignment { case .fill: pinToSuperView(edges) case .leading: pinTop(edges.top) pinLeading(edges.left) pinTrailingLessThanOrEqualTo(anchor: superview.trailingAnchor, constant: edges.right) pinBottom(edges.bottom) case .trailing: pinTop(edges.top) pinLeadingGreaterThanOrEqualTo(anchor: superview.leadingAnchor, constant: edges.left) pinTrailing(edges.right) pinBottom(edges.bottom) case .top: pinTop(edges.top) pinLeadingGreaterThanOrEqualTo(anchor: superview.leadingAnchor, constant: edges.left) pinTrailingLessThanOrEqualTo(anchor: superview.trailingAnchor, constant: edges.right) pinBottomLessThanOrEqualTo(anchor: superview.bottomAnchor, constant: edges.bottom) case .bottom: pinTopGreaterThanOrEqualTo(anchor: superview.topAnchor, constant: edges.top) pinLeadingGreaterThanOrEqualTo(anchor: superview.leadingAnchor, constant: edges.left) pinTrailingLessThanOrEqualTo(anchor: superview.trailingAnchor, constant: edges.right) pinBottom(edges.bottom) case .center: pinCenterX() pinTop(edges.top) pinLeadingGreaterThanOrEqualTo(anchor: superview.leadingAnchor, constant: edges.left) pinTrailingLessThanOrEqualTo(anchor: superview.trailingAnchor, constant: edges.right) pinBottom(edges.bottom) } } // Method to check if the view is pinned to its superview public func isPinnedEqual() -> Bool { isPinnedEqualVertically() && isPinnedEqualHorizontally() } public func horizontalPinnedWidth() -> CGFloat? { guard let view = self as? UIView, let superview = view.superview else { return nil } let constraints = superview.constraints var leadingPinnedObject: AnyObject? var trailingPinnedObject: AnyObject? for constraint in constraints { if (constraint.firstItem === view && (constraint.firstAttribute == .leading || constraint.firstAttribute == .left)) { leadingPinnedObject = constraint.secondItem as AnyObject? } else if (constraint.secondItem === view && (constraint.secondAttribute == .leading || constraint.secondAttribute == .left)) { leadingPinnedObject = constraint.firstItem as AnyObject? } else if (constraint.firstItem === view && (constraint.firstAttribute == .trailing || constraint.firstAttribute == .right)) { trailingPinnedObject = constraint.secondItem as AnyObject? } else if (constraint.secondItem === view && (constraint.secondAttribute == .trailing || constraint.secondAttribute == .right)) { trailingPinnedObject = constraint.firstItem as AnyObject? } } // Ensure both leading and trailing pinned objects are identified if let leadingObject = leadingPinnedObject, let trailingObject = trailingPinnedObject { // Calculate the size based on the pinned objects if let leadingView = leadingObject as? UIView, let trailingView = trailingObject as? UIView { let leadingPosition = leadingView.convert(leadingView.bounds.origin, to: superview).x let trailingPosition = trailingView.convert(trailingView.bounds.origin, to: superview).x + trailingView.bounds.width return trailingPosition - leadingPosition } else if let leadingGuide = leadingObject as? UILayoutGuide, let trailingGuide = trailingObject as? UILayoutGuide { let leadingPosition = leadingGuide.layoutFrame.minX let trailingPosition = trailingGuide.layoutFrame.maxX return trailingPosition - leadingPosition } else if let leadingView = leadingObject as? UIView, let trailingGuide = trailingObject as? UILayoutGuide { let leadingPosition = leadingView.convert(leadingView.bounds.origin, to: superview).x let trailingPosition = trailingGuide.layoutFrame.maxX return trailingPosition - leadingPosition } else if let leadingGuide = leadingObject as? UILayoutGuide, let trailingView = trailingObject as? UIView { let leadingPosition = leadingGuide.layoutFrame.minX let trailingPosition = trailingView.convert(trailingView.bounds.origin, to: superview).x + trailingView.bounds.width return trailingPosition - leadingPosition } } else if let pinnedObject = leadingPinnedObject { if let view = pinnedObject as? UIView { return view.bounds.size.width } else if let layoutGuide = pinnedObject as? UILayoutGuide { return layoutGuide.layoutFrame.size.width } } else if let pinnedObject = trailingPinnedObject { if let view = pinnedObject as? UIView { return view.bounds.size.width } else if let layoutGuide = pinnedObject as? UILayoutGuide { return layoutGuide.layoutFrame.size.width } } return nil } public func verticalPinnedHeight() -> CGFloat? { guard let view = self as? UIView, let superview = view.superview else { return nil } let constraints = superview.constraints var topPinnedObject: AnyObject? var bottomPinnedObject: AnyObject? for constraint in constraints { if (constraint.firstItem === view && (constraint.firstAttribute == .top || constraint.firstAttribute == .topMargin)) { topPinnedObject = constraint.secondItem as AnyObject? } else if (constraint.secondItem === view && (constraint.secondAttribute == .top || constraint.secondAttribute == .topMargin)) { topPinnedObject = constraint.firstItem as AnyObject? } else if (constraint.firstItem === view && (constraint.firstAttribute == .bottom || constraint.firstAttribute == .bottomMargin)) { bottomPinnedObject = constraint.secondItem as AnyObject? } else if (constraint.secondItem === view && (constraint.secondAttribute == .bottom || constraint.secondAttribute == .bottomMargin)) { bottomPinnedObject = constraint.firstItem as AnyObject? } } // Ensure both top and bottom pinned objects are identified if let topObject = topPinnedObject, let bottomObject = bottomPinnedObject { // Calculate the size based on the pinned objects if let topView = topObject as? UIView, let bottomView = bottomObject as? UIView { let topPosition = topView.convert(topView.bounds.origin, to: superview).y let bottomPosition = bottomView.convert(bottomView.bounds.origin, to: superview).y + bottomView.bounds.height return bottomPosition - topPosition } else if let topGuide = topObject as? UILayoutGuide, let bottomGuide = bottomObject as? UILayoutGuide { let topPosition = topGuide.layoutFrame.minY let bottomPosition = bottomGuide.layoutFrame.maxY return bottomPosition - topPosition } else if let topView = topObject as? UIView, let bottomGuide = bottomObject as? UILayoutGuide { let topPosition = topView.convert(topView.bounds.origin, to: superview).y let bottomPosition = bottomGuide.layoutFrame.maxY return bottomPosition - topPosition } else if let topGuide = topObject as? UILayoutGuide, let bottomView = bottomObject as? UIView { let topPosition = topGuide.layoutFrame.minY let bottomPosition = bottomView.convert(bottomView.bounds.origin, to: superview).y + bottomView.bounds.height return bottomPosition - topPosition } } else if let pinnedObject = topPinnedObject { if let view = pinnedObject as? UIView { return view.bounds.size.height } else if let layoutGuide = pinnedObject as? UILayoutGuide { return layoutGuide.layoutFrame.size.height } } else if let pinnedObject = bottomPinnedObject { if let view = pinnedObject as? UIView { return view.bounds.size.height } else if let layoutGuide = pinnedObject as? UILayoutGuide { return layoutGuide.layoutFrame.size.height } } return nil } public func isPinnedEqualHorizontally() -> Bool { guard let view = self as? UIView, let superview = view.superview else { return false } let constraints = superview.constraints var leadingPinned = false var trailingPinned = false for constraint in constraints { if (constraint.firstItem as? UIView == view && constraint.firstAttribute == .leading && constraint.relation == .equal) || (constraint.secondItem as? UIView == view && constraint.secondAttribute == .leading && constraint.relation == .equal) || (constraint.firstItem as? UIView == view && constraint.firstAttribute == .left && constraint.relation == .equal) || (constraint.secondItem as? UIView == view && constraint.secondAttribute == .left && constraint.relation == .equal) { leadingPinned = true } if (constraint.firstItem as? UIView == view && constraint.firstAttribute == .trailing && constraint.relation == .equal) || (constraint.secondItem as? UIView == view && constraint.secondAttribute == .trailing && constraint.relation == .equal) || (constraint.firstItem as? UIView == view && constraint.firstAttribute == .right && constraint.relation == .equal) || (constraint.secondItem as? UIView == view && constraint.secondAttribute == .right && constraint.relation == .equal) { trailingPinned = true } } return leadingPinned && trailingPinned } public func isPinnedEqualVertically() -> Bool { guard let view = self as? UIView, let superview = view.superview else { return false } let constraints = superview.constraints var topPinned = false var bottomPinned = false for constraint in constraints { if (constraint.firstItem as? UIView == view && constraint.firstAttribute == .top && constraint.relation == .equal) || (constraint.secondItem as? UIView == view && constraint.secondAttribute == .top && constraint.relation == .equal) { topPinned = true } if (constraint.firstItem as? UIView == view && constraint.firstAttribute == .bottom && constraint.relation == .equal) || (constraint.secondItem as? UIView == view && constraint.secondAttribute == .bottom && constraint.relation == .equal) { bottomPinned = true } } return topPinned && bottomPinned } } //-------------------------------------------------- // MARK: - Implementations //-------------------------------------------------- extension UIView: LayoutConstraintable {} extension UILayoutGuide: LayoutConstraintable { public var superview: UIView? { owningView } }