added container

Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
This commit is contained in:
Matt Bruce 2023-09-05 11:02:34 -05:00
parent 01a177f643
commit 5ebc7408f5
2 changed files with 640 additions and 0 deletions

View File

@ -15,6 +15,7 @@
EA0B18022A9E236900F2D0CD /* SelectorGroupBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0B18012A9E236900F2D0CD /* SelectorGroupBase.swift */; }; EA0B18022A9E236900F2D0CD /* SelectorGroupBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0B18012A9E236900F2D0CD /* SelectorGroupBase.swift */; };
EA0B18052A9E2D2D00F2D0CD /* SelectorBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0B18032A9E2D2D00F2D0CD /* SelectorBase.swift */; }; EA0B18052A9E2D2D00F2D0CD /* SelectorBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0B18032A9E2D2D00F2D0CD /* SelectorBase.swift */; };
EA0B18062A9E2D2D00F2D0CD /* SelectorItemBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0B18042A9E2D2D00F2D0CD /* SelectorItemBase.swift */; }; EA0B18062A9E2D2D00F2D0CD /* SelectorItemBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0B18042A9E2D2D00F2D0CD /* SelectorItemBase.swift */; };
EA0B18082AA22A8500F2D0CD /* Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0B18072AA22A8500F2D0CD /* Container.swift */; };
EA0D1C372A681CCE00E5C127 /* ToggleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0D1C362A681CCE00E5C127 /* ToggleView.swift */; }; EA0D1C372A681CCE00E5C127 /* ToggleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0D1C362A681CCE00E5C127 /* ToggleView.swift */; };
EA0D1C392A6AD4DF00E5C127 /* Typography+SpacingConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0D1C382A6AD4DF00E5C127 /* Typography+SpacingConfig.swift */; }; EA0D1C392A6AD4DF00E5C127 /* Typography+SpacingConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0D1C382A6AD4DF00E5C127 /* Typography+SpacingConfig.swift */; };
EA0D1C3B2A6AD51B00E5C127 /* Typogprahy+Styles.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0D1C3A2A6AD51B00E5C127 /* Typogprahy+Styles.swift */; }; EA0D1C3B2A6AD51B00E5C127 /* Typogprahy+Styles.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0D1C3A2A6AD51B00E5C127 /* Typogprahy+Styles.swift */; };
@ -160,6 +161,7 @@
EA0B18012A9E236900F2D0CD /* SelectorGroupBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectorGroupBase.swift; sourceTree = "<group>"; }; EA0B18012A9E236900F2D0CD /* SelectorGroupBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectorGroupBase.swift; sourceTree = "<group>"; };
EA0B18032A9E2D2D00F2D0CD /* SelectorBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectorBase.swift; sourceTree = "<group>"; }; EA0B18032A9E2D2D00F2D0CD /* SelectorBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectorBase.swift; sourceTree = "<group>"; };
EA0B18042A9E2D2D00F2D0CD /* SelectorItemBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectorItemBase.swift; sourceTree = "<group>"; }; EA0B18042A9E2D2D00F2D0CD /* SelectorItemBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectorItemBase.swift; sourceTree = "<group>"; };
EA0B18072AA22A8500F2D0CD /* Container.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Container.swift; sourceTree = "<group>"; };
EA0D1C362A681CCE00E5C127 /* ToggleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleView.swift; sourceTree = "<group>"; }; EA0D1C362A681CCE00E5C127 /* ToggleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleView.swift; sourceTree = "<group>"; };
EA0D1C382A6AD4DF00E5C127 /* Typography+SpacingConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Typography+SpacingConfig.swift"; sourceTree = "<group>"; }; EA0D1C382A6AD4DF00E5C127 /* Typography+SpacingConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Typography+SpacingConfig.swift"; sourceTree = "<group>"; };
EA0D1C3A2A6AD51B00E5C127 /* Typogprahy+Styles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Typogprahy+Styles.swift"; sourceTree = "<group>"; }; EA0D1C3A2A6AD51B00E5C127 /* Typogprahy+Styles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Typogprahy+Styles.swift"; sourceTree = "<group>"; };
@ -520,6 +522,7 @@
EA985C1C296CD13600F2FF2E /* BundleManager.swift */, EA985C1C296CD13600F2FF2E /* BundleManager.swift */,
EAF7F0B8289C139800B287F5 /* ColorConfiguration.swift */, EAF7F0B8289C139800B287F5 /* ColorConfiguration.swift */,
EAB5FEF02927F4AA00998C17 /* SelfSizingCollectionView.swift */, EAB5FEF02927F4AA00998C17 /* SelfSizingCollectionView.swift */,
EA0B18072AA22A8500F2D0CD /* Container.swift */,
); );
path = Classes; path = Classes;
sourceTree = "<group>"; sourceTree = "<group>";
@ -937,6 +940,7 @@
EAF7F11728A1475A00B287F5 /* RadioButtonItem.swift in Sources */, EAF7F11728A1475A00B287F5 /* RadioButtonItem.swift in Sources */,
EA985BEE2968A92400F2FF2E /* TitleLockupSubTitleModel.swift in Sources */, EA985BEE2968A92400F2FF2E /* TitleLockupSubTitleModel.swift in Sources */,
EA985BF22968B5BB00F2FF2E /* TitleLockupTextStyle.swift in Sources */, EA985BF22968B5BB00F2FF2E /* TitleLockupTextStyle.swift in Sources */,
EA0B18082AA22A8500F2D0CD /* Container.swift in Sources */,
EAB1D2CD28ABE76100DAE764 /* Withable.swift in Sources */, EAB1D2CD28ABE76100DAE764 /* Withable.swift in Sources */,
EAC846F3294B95CE00F685BA /* ButtonGroupCollectionViewCell.swift in Sources */, EAC846F3294B95CE00F685BA /* ButtonGroupCollectionViewCell.swift in Sources */,
EAF7F0952899861000B287F5 /* CheckboxItem.swift in Sources */, EAF7F0952899861000B287F5 /* CheckboxItem.swift in Sources */,

636
VDS/Classes/Container.swift Normal file
View File

@ -0,0 +1,636 @@
//
// Container.swift
// VDS
//
// Created by Matt Bruce on 9/1/23.
//
import Foundation
import UIKit
open class Container: View {
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
required public init() {
super.init(frame: .zero)
}
public override init(frame: CGRect) {
super.init(frame: .zero)
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
}
//--------------------------------------------------
// MARK: - Private Properties
//--------------------------------------------------
private let constainerManager = ContainerManager()
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
public var view: UIView? { didSet { setNeedsUpdate() } }
public var containerModel: ContainerManager.ContainerModel? { didSet { setNeedsUpdate() } }
//--------------------------------------------------
// MARK: - Overrides
//--------------------------------------------------
open override func setup() {
super.setup()
isAccessibilityElement = false
}
override open func reset() {
super.reset()
(view as? ViewProtocol)?.reset()
}
open override func updateView() {
super.updateView()
if var view = view as? ViewProtocol {
view.isEnabled = isEnabled
view.surface = surface
}
}
open override func updateAccessibility() {
super.updateAccessibility()
guard let view, let superView = view.superview else { return }
superView.isAccessibilityElement = false
if let elements = view.accessibilityElements {
superView.accessibilityElements = elements
} else {
superView.accessibilityElements = [view]
}
}
}
open class ContainerManager {
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
public init() {}
//--------------------------------------------------
// MARK: - Constraints
//--------------------------------------------------
private var topConstraint: NSLayoutConstraint?
private var leadingConstraint: NSLayoutConstraint?
private var bottomConstraint: NSLayoutConstraint?
private var trailingConstraint: NSLayoutConstraint?
private var alignCenterHorizontalConstraint: NSLayoutConstraint?
private var alignCenterLeadingConstraint: NSLayoutConstraint?
private var alignCenterTrailingConstraint: NSLayoutConstraint?
private var alignCenterVerticalConstraint: NSLayoutConstraint?
private var alignCenterTopConstraint: NSLayoutConstraint?
private var alignCenterBottomConstraint: NSLayoutConstraint?
//--------------------------------------------------
// MARK: - Public Methods
//--------------------------------------------------
open func constrain(view: UIView, with model: ContainerModel) {
addConstraints(for: view, model: model)
if let horizontalAlignment = model.horizontalAlignment {
alignHorizontal(horizontalAlignment.alignment)
}
if let verticalAlignment = model.verticalAlignment {
alignVertical(verticalAlignment.alignment)
}
}
private func addConstraints(for view: UIView, model: ContainerModel) {
guard let margins = view.superview?.layoutMarginsGuide else { return }
if let horizontal = model.horizontalAlignment {
switch horizontal.alignment {
case .fill:
leadingConstraint = view.leadingAnchor.constraint(equalTo: margins.leadingAnchor)
trailingConstraint = margins.trailingAnchor.constraint(equalTo: view.trailingAnchor)
case .leading:
leadingConstraint = view.leadingAnchor.constraint(equalTo: margins.leadingAnchor)
trailingConstraint = margins.trailingAnchor.constraint(greaterThanOrEqualTo: view.trailingAnchor)
case .trailing:
leadingConstraint = view.leadingAnchor.constraint(greaterThanOrEqualTo: margins.leadingAnchor)
trailingConstraint = margins.trailingAnchor.constraint(equalTo: view.trailingAnchor)
default:
break
}
leadingConstraint?.priority = horizontal.leading.priority
leadingConstraint?.constant = horizontal.leading.padding
trailingConstraint?.priority = horizontal.leading.priority
trailingConstraint?.constant = horizontal.leading.padding
}
if let vertical = model.verticalAlignment {
switch vertical.alignment {
case .fill:
topConstraint = view.topAnchor.constraint(equalTo: margins.topAnchor)
bottomConstraint = margins.bottomAnchor.constraint(equalTo: view.bottomAnchor)
case .top:
topConstraint = view.topAnchor.constraint(equalTo: margins.topAnchor)
bottomConstraint = margins.bottomAnchor.constraint(greaterThanOrEqualTo: view.bottomAnchor)
case .bottom:
topConstraint = view.topAnchor.constraint(greaterThanOrEqualTo: margins.topAnchor)
bottomConstraint = margins.bottomAnchor.constraint(greaterThanOrEqualTo: view.bottomAnchor)
default:
break
}
topConstraint?.priority = vertical.top.priority
topConstraint?.constant = vertical.top.padding
bottomConstraint?.priority = vertical.bottom.priority
bottomConstraint?.constant = vertical.bottom.padding
}
alignCenterHorizontalConstraint = view.centerXAnchor.constraint(equalTo: margins.centerXAnchor)
alignCenterLeadingConstraint = view.leadingAnchor.constraint(greaterThanOrEqualTo: margins.leadingAnchor)
alignCenterTrailingConstraint = margins.trailingAnchor.constraint(greaterThanOrEqualTo: view.trailingAnchor)
alignCenterVerticalConstraint = view.centerYAnchor.constraint(equalTo: margins.centerYAnchor)
alignCenterTopConstraint = view.topAnchor.constraint(greaterThanOrEqualTo: margins.topAnchor)
alignCenterBottomConstraint = margins.bottomAnchor.constraint(greaterThanOrEqualTo: view.bottomAnchor)
}
private func alignHorizontal(_ alignment: UIStackView.Alignment) {
switch alignment {
case .center:
alignCenterHorizontalConstraint?.isActive = true
alignCenterLeadingConstraint?.isActive = true
alignCenterTrailingConstraint?.isActive = true
leadingConstraint?.isActive = false
trailingConstraint?.isActive = false
case .leading:
alignCenterHorizontalConstraint?.isActive = false
alignCenterLeadingConstraint?.isActive = false
alignCenterTrailingConstraint?.isActive = true
leadingConstraint?.isActive = true
trailingConstraint?.isActive = false
case .trailing:
alignCenterHorizontalConstraint?.isActive = false
alignCenterLeadingConstraint?.isActive = true
alignCenterTrailingConstraint?.isActive = false
leadingConstraint?.isActive = false
trailingConstraint?.isActive = true
case .fill:
alignCenterHorizontalConstraint?.isActive = false
alignCenterLeadingConstraint?.isActive = false
alignCenterTrailingConstraint?.isActive = false
leadingConstraint?.isActive = true
trailingConstraint?.isActive = true
default: break
}
}
private func alignVertical(_ alignment: UIStackView.Alignment) {
switch alignment {
case .center:
alignCenterVerticalConstraint?.isActive = true
alignCenterTopConstraint?.isActive = true
alignCenterBottomConstraint?.isActive = true
topConstraint?.isActive = false
bottomConstraint?.isActive = false
case .leading:
alignCenterVerticalConstraint?.isActive = false
alignCenterTopConstraint?.isActive = false
alignCenterBottomConstraint?.isActive = true
topConstraint?.isActive = true
bottomConstraint?.isActive = false
case .trailing:
alignCenterVerticalConstraint?.isActive = false
alignCenterTopConstraint?.isActive = true
alignCenterBottomConstraint?.isActive = false
topConstraint?.isActive = false
bottomConstraint?.isActive = true
case .fill:
alignCenterVerticalConstraint?.isActive = false
alignCenterTopConstraint?.isActive = false
alignCenterBottomConstraint?.isActive = false
topConstraint?.isActive = true
bottomConstraint?.isActive = true
default: break
}
}
}
extension ContainerManager {
public static func model(for horizontal: UIStackView.Alignment, vertical: UIStackView.Alignment, inset: UIEdgeInsets = .zero) -> ContainerModel {
.init(horizontal: .init(alignment: horizontal, leading: .init(padding: inset.left), trailing: .init(padding: inset.right)),
vertical: .init(alignment: vertical, top: .init(padding: inset.top), bottom: .init(padding: inset.bottom)))
}
public struct Constraint {
public let padding: CGFloat
public let priority: UILayoutPriority
public init(padding: CGFloat = 0, priority: UILayoutPriority = .required) {
self.padding = padding
self.priority = priority
}
}
public struct HorizontalAlignment {
public let alignment: UIStackView.Alignment
public let leading: Constraint
public let trailing: Constraint
public init(alignment: UIStackView.Alignment = .fill,
leading: Constraint = .init(),
trailing: Constraint = .init()) {
self.alignment = alignment
self.leading = leading
self.trailing = trailing
}
}
public struct VerticalAlignment {
public let alignment: UIStackView.Alignment
public let top: Constraint
public let bottom: Constraint
public init(alignment: UIStackView.Alignment = .fill,
top: Constraint = .init(),
bottom: Constraint = .init()) {
self.alignment = alignment
self.top = top
self.bottom = bottom
}
}
public struct ContainerModel {
public var horizontalAlignment: HorizontalAlignment?
public var verticalAlignment: VerticalAlignment?
public init(horizontal: HorizontalAlignment?, vertical: VerticalAlignment?) {
self.horizontalAlignment = horizontal
self.verticalAlignment = vertical
}
}
}
import UIKit
/// A protocol representing layout anchor providing objects like `UIView` and `UILayoutGuide`.
public protocol LayoutAnchorProviding {
var leadingAnchor: NSLayoutXAxisAnchor { get }
var trailingAnchor: NSLayoutXAxisAnchor { get }
var leftAnchor: NSLayoutXAxisAnchor { get }
var rightAnchor: NSLayoutXAxisAnchor { get }
var topAnchor: NSLayoutYAxisAnchor { get }
var bottomAnchor: NSLayoutYAxisAnchor { get }
var centerXAnchor: NSLayoutXAxisAnchor { get }
var centerYAnchor: NSLayoutYAxisAnchor { get }
var widthAnchor: NSLayoutDimension { get }
var heightAnchor: NSLayoutDimension { get }
}
extension UIView: LayoutAnchorProviding {}
extension UILayoutGuide: LayoutAnchorProviding {}
/// An enumeration representing different layout types for positioning.
public enum LayoutType {
case fill
case topLeft
case topCenter
case topRight
case left
case center
case right
case bottomLeft
case bottomCenter
case bottomRight
}
/// An enumeration representing different anchor types.
public enum AnchorType: String {
case leading = "LayoutAnchorProvidingLeading"
case trailing = "LayoutAnchorProvidingTrailing"
case top = "LayoutAnchorProvidingTop"
case bottom = "LayoutAnchorProvidingBottom"
case centerX = "LayoutAnchorProvidingCenterX"
case centerY = "LayoutAnchorProvidingCenterY"
}
/// An enumeration representing stack orientation.
public enum StackOrientation {
case horizontal
case vertical
}
public struct LayoutAnchorModel {
public let layoutType: LayoutType
public let insets: UIEdgeInsets
public let priorities: [AnchorType: UILayoutPriority]
public init (layoutType: LayoutType = .fill, insets: UIEdgeInsets = .zero, priorities: [AnchorType: UILayoutPriority] = [:]) {
self.layoutType = layoutType
self.insets = insets
self.priorities = priorities
}
}
// Extension for UIView
public extension UIView {
/// Pins the view to a `LayoutAnchorProviding` object based on the specified layout type, edge insets, and priorities.
/// - Parameters:
/// - layoutProvider: The `LayoutAnchorProviding` object to pin to.
/// - layoutType: The `LayoutType` specifying how to pin.
/// - edgeInsets: The `UIEdgeInsets` for the pinning.
/// - priorities: The `UILayoutPriority` dictionary for each anchor type.
func pin(
to layoutProvider: LayoutAnchorProviding,
with layoutType: LayoutType,
edgeInsets: UIEdgeInsets = .zero,
priorities: [AnchorType: UILayoutPriority] = [:]
) {
translatesAutoresizingMaskIntoConstraints = false
var leadingConstraint: NSLayoutConstraint?
var trailingConstraint: NSLayoutConstraint?
var topConstraint: NSLayoutConstraint?
var bottomConstraint: NSLayoutConstraint?
var centerXConstraint: NSLayoutConstraint?
var centerYConstraint: NSLayoutConstraint?
switch layoutType {
case .fill:
leadingConstraint = leadingAnchor.constraint(equalTo: layoutProvider.leadingAnchor, constant: edgeInsets.left)
trailingConstraint = trailingAnchor.constraint(equalTo: layoutProvider.trailingAnchor, constant: -edgeInsets.right)
topConstraint = topAnchor.constraint(equalTo: layoutProvider.topAnchor, constant: edgeInsets.top)
bottomConstraint = bottomAnchor.constraint(equalTo: layoutProvider.bottomAnchor, constant: -edgeInsets.bottom)
case .topLeft:
leadingConstraint = leadingAnchor.constraint(equalTo: layoutProvider.leadingAnchor, constant: edgeInsets.left)
topConstraint = topAnchor.constraint(equalTo: layoutProvider.topAnchor, constant: edgeInsets.top)
trailingConstraint = trailingAnchor.constraint(lessThanOrEqualTo: layoutProvider.trailingAnchor, constant: -edgeInsets.right)
bottomConstraint = bottomAnchor.constraint(lessThanOrEqualTo: layoutProvider.bottomAnchor, constant: -edgeInsets.bottom)
case .topCenter:
centerXConstraint = centerXAnchor.constraint(equalTo: layoutProvider.centerXAnchor)
topConstraint = topAnchor.constraint(equalTo: layoutProvider.topAnchor, constant: edgeInsets.top)
leadingConstraint = leadingAnchor.constraint(greaterThanOrEqualTo: layoutProvider.leadingAnchor, constant: edgeInsets.left)
trailingConstraint = trailingAnchor.constraint(lessThanOrEqualTo: layoutProvider.trailingAnchor, constant: -edgeInsets.right)
bottomConstraint = bottomAnchor.constraint(lessThanOrEqualTo: layoutProvider.bottomAnchor, constant: -edgeInsets.bottom)
case .topRight:
trailingConstraint = trailingAnchor.constraint(equalTo: layoutProvider.trailingAnchor, constant: -edgeInsets.right)
topConstraint = topAnchor.constraint(equalTo: layoutProvider.topAnchor, constant: edgeInsets.top)
leadingConstraint = leadingAnchor.constraint(greaterThanOrEqualTo: layoutProvider.leadingAnchor, constant: edgeInsets.left)
bottomConstraint = bottomAnchor.constraint(lessThanOrEqualTo: layoutProvider.bottomAnchor, constant: -edgeInsets.bottom)
case .left:
leadingConstraint = leadingAnchor.constraint(equalTo: layoutProvider.leadingAnchor, constant: edgeInsets.left)
centerYConstraint = centerYAnchor.constraint(equalTo: layoutProvider.centerYAnchor)
trailingConstraint = trailingAnchor.constraint(lessThanOrEqualTo: layoutProvider.trailingAnchor, constant: -edgeInsets.right)
topConstraint = topAnchor.constraint(equalTo: layoutProvider.topAnchor, constant: edgeInsets.top)
bottomConstraint = bottomAnchor.constraint(equalTo: layoutProvider.bottomAnchor, constant: -edgeInsets.bottom)
case .center:
centerXConstraint = centerXAnchor.constraint(equalTo: layoutProvider.centerXAnchor)
centerYConstraint = centerYAnchor.constraint(equalTo: layoutProvider.centerYAnchor)
leadingConstraint = leadingAnchor.constraint(greaterThanOrEqualTo: layoutProvider.leadingAnchor, constant: edgeInsets.left)
trailingConstraint = trailingAnchor.constraint(lessThanOrEqualTo: layoutProvider.trailingAnchor, constant: -edgeInsets.right)
topConstraint = topAnchor.constraint(greaterThanOrEqualTo: layoutProvider.topAnchor, constant: edgeInsets.top)
bottomConstraint = bottomAnchor.constraint(lessThanOrEqualTo: layoutProvider.bottomAnchor, constant: -edgeInsets.bottom)
case .right:
trailingConstraint = trailingAnchor.constraint(equalTo: layoutProvider.trailingAnchor, constant: -edgeInsets.right)
centerYConstraint = centerYAnchor.constraint(equalTo: layoutProvider.centerYAnchor)
leadingConstraint = leadingAnchor.constraint(greaterThanOrEqualTo: layoutProvider.leadingAnchor, constant: edgeInsets.left)
topConstraint = topAnchor.constraint(equalTo: layoutProvider.topAnchor, constant: edgeInsets.top)
bottomConstraint = bottomAnchor.constraint(equalTo: layoutProvider.bottomAnchor, constant: -edgeInsets.bottom)
case .bottomLeft:
leadingConstraint = leadingAnchor.constraint(equalTo: layoutProvider.leadingAnchor, constant: edgeInsets.left)
bottomConstraint = bottomAnchor.constraint(equalTo: layoutProvider.bottomAnchor, constant: -edgeInsets.bottom)
trailingConstraint = trailingAnchor.constraint(lessThanOrEqualTo: layoutProvider.trailingAnchor, constant: -edgeInsets.right)
topConstraint = topAnchor.constraint(greaterThanOrEqualTo: layoutProvider.topAnchor, constant: edgeInsets.top)
case .bottomCenter:
centerXConstraint = centerXAnchor.constraint(equalTo: layoutProvider.centerXAnchor)
bottomConstraint = bottomAnchor.constraint(equalTo: layoutProvider.bottomAnchor, constant: -edgeInsets.bottom)
leadingConstraint = leadingAnchor.constraint(greaterThanOrEqualTo: layoutProvider.leadingAnchor, constant: edgeInsets.left)
trailingConstraint = trailingAnchor.constraint(lessThanOrEqualTo: layoutProvider.trailingAnchor, constant: -edgeInsets.right)
topConstraint = topAnchor.constraint(greaterThanOrEqualTo: layoutProvider.topAnchor, constant: edgeInsets.top)
case .bottomRight:
trailingConstraint = trailingAnchor.constraint(equalTo: layoutProvider.trailingAnchor, constant: -edgeInsets.right)
bottomConstraint = bottomAnchor.constraint(equalTo: layoutProvider.bottomAnchor, constant: -edgeInsets.bottom)
leadingConstraint = leadingAnchor.constraint(greaterThanOrEqualTo: layoutProvider.leadingAnchor, constant: edgeInsets.left)
topConstraint = topAnchor.constraint(greaterThanOrEqualTo: layoutProvider.topAnchor, constant: edgeInsets.top)
}
// Set identifiers
leadingConstraint?.identifier = AnchorType.leading.rawValue
trailingConstraint?.identifier = AnchorType.trailing.rawValue
topConstraint?.identifier = AnchorType.top.rawValue
bottomConstraint?.identifier = AnchorType.bottom.rawValue
centerXConstraint?.identifier = AnchorType.centerX.rawValue
centerYConstraint?.identifier = AnchorType.centerY.rawValue
// Set priorities
leadingConstraint?.priority = priorities[.leading] ?? .required
trailingConstraint?.priority = priorities[.trailing] ?? .required
topConstraint?.priority = priorities[.top] ?? .required
bottomConstraint?.priority = priorities[.bottom] ?? .required
centerXConstraint?.priority = priorities[.centerX] ?? .required
centerYConstraint?.priority = priorities[.centerY] ?? .required
// Activate constraints
let constraints = [leadingConstraint, trailingConstraint, topConstraint, bottomConstraint, centerXConstraint, centerYConstraint].compactMap { $0 }
NSLayoutConstraint.activate(constraints)
}
/// Pins the view to its superview based on the specified layout type, edge insets, and priorities.
/// - Parameters:
/// - layoutType: The `LayoutType` specifying how to pin.
/// - edgeInsets: The `UIEdgeInsets` for the pinning.
/// - priorities: The `UILayoutPriority` dictionary for each anchor type.
func pin(
to layoutProvider: LayoutAnchorProviding,
with model: LayoutAnchorModel
) {
pin(to: layoutProvider, with: model.layoutType, edgeInsets: model.insets, priorities: model.priorities)
}
/// Pins the view to its superview based on the specified layout type, edge insets, and priorities.
/// - Parameters:
/// - layoutType: The `LayoutType` specifying how to pin.
/// - edgeInsets: The `UIEdgeInsets` for the pinning.
/// - priorities: The `UILayoutPriority` dictionary for each anchor type.
func pinToSuperview(
with layoutType: LayoutType,
edgeInsets: UIEdgeInsets = .zero,
priorities: [AnchorType: UILayoutPriority] = [:]
) {
guard let superview = superview else {
print("Superview is nil")
return
}
pin(to: superview, with: layoutType, edgeInsets: edgeInsets, priorities: priorities)
}
/// Pins the view to its superview based on the specified layout type, edge insets, and priorities.
/// - Parameters:
/// - layoutType: The `LayoutType` specifying how to pin.
/// - edgeInsets: The `UIEdgeInsets` for the pinning.
/// - priorities: The `UILayoutPriority` dictionary for each anchor type.
func pinToSuperview(
with model: LayoutAnchorModel
) {
guard let superview = superview else {
print("Superview is nil")
return
}
pin(to: superview, with: model)
}
/// Sets the aspect ratio for the view.
/// - Parameters:
/// - aspectRatio: The aspect ratio as a `CGFloat`.
/// - priority: The `UILayoutPriority` for the constraint. Defaults to `.required`.
func setAspectRatio(_ aspectRatio: CGFloat, priority: UILayoutPriority = .required) {
let constraint = widthAnchor.constraint(equalTo: heightAnchor, multiplier: aspectRatio)
constraint.priority = priority
constraint.isActive = true
}
/// Pins the view to the safe area of its superview.
/// - Parameters:
/// - layoutType: The `LayoutType` specifying how to pin.
/// - edgeInsets: The `UIEdgeInsets` for the pinning.
/// - priorities: The `UILayoutPriority` dictionary for each anchor type.
func pinToSafeArea(
with layoutType: LayoutType,
edgeInsets: UIEdgeInsets = .zero,
priorities: [AnchorType: UILayoutPriority] = [:]
) {
guard let superview = superview else {
print("Superview is nil")
return
}
let safeArea = superview.safeAreaLayoutGuide
pin(to: safeArea, with: layoutType, edgeInsets: edgeInsets, priorities: priorities)
}
/// Stacks an array of `LayoutAnchorProviding` objects in a specified orientation.
/// - Parameters:
/// - views: The array of `LayoutAnchorProviding` objects.
/// - orientation: The `StackOrientation` specifying how to stack.
/// - spacing: The spacing between the objects.
func stack(
views: [LayoutAnchorProviding],
orientation: StackOrientation,
spacing: CGFloat
) {
var previousView: LayoutAnchorProviding?
for view in views {
if let previousView = previousView {
switch orientation {
case .horizontal:
let constraint = view.leadingAnchor.constraint(equalTo: previousView.trailingAnchor, constant: spacing)
constraint.isActive = true
case .vertical:
let constraint = view.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: spacing)
constraint.isActive = true
}
}
previousView = view
}
}
/// Sets the minimum width for the view.
/// - Parameters:
/// - width: The minimum width as a `CGFloat`.
/// - priority: The `UILayoutPriority` for the constraint. Defaults to `.required`.
func setMinWidth(_ width: CGFloat, priority: UILayoutPriority = .required) {
let constraint = widthAnchor.constraint(greaterThanOrEqualToConstant: width)
constraint.priority = priority
constraint.isActive = true
}
/// Sets the maximum width for the view.
/// - Parameters:
/// - width: The maximum width as a `CGFloat`.
/// - priority: The `UILayoutPriority` for the constraint. Defaults to `.required`.
func setMaxWidth(_ width: CGFloat, priority: UILayoutPriority = .required) {
let constraint = widthAnchor.constraint(lessThanOrEqualToConstant: width)
constraint.priority = priority
constraint.isActive = true
}
/// Sets the minimum height for the view.
/// - Parameters:
/// - height: The minimum height as a `CGFloat`.
/// - priority: The `UILayoutPriority` for the constraint. Defaults to `.required`.
func setMinHeight(_ height: CGFloat, priority: UILayoutPriority = .required) {
let constraint = heightAnchor.constraint(greaterThanOrEqualToConstant: height)
constraint.priority = priority
constraint.isActive = true
}
/// Sets the maximum height for the view.
/// - Parameters:
/// - height: The maximum height as a `CGFloat`.
/// - priority: The `UILayoutPriority` for the constraint. Defaults to `.required`.
func setMaxHeight(_ height: CGFloat, priority: UILayoutPriority = .required) {
let constraint = heightAnchor.constraint(lessThanOrEqualToConstant: height)
constraint.priority = priority
constraint.isActive = true
}
/// Updates a constraint based on its identifier.
/// - Parameters:
/// - identifier: The identifier of the constraint to find.
/// - constant: The new constant value to set. Defaults to nil.
/// - priority: The new priority to set. Defaults to nil.
func updateConstraint(withIdentifier identifier: String, constant: CGFloat? = nil, priority: UILayoutPriority? = nil) {
if let constraint = constraints.first(where: { $0.identifier == identifier }) {
if let constant = constant {
constraint.constant = constant
}
if let priority = priority {
constraint.priority = priority
}
} else {
print("Constraint with identifier \(identifier) not found.")
}
}
/// Updates a constraint based on its identifier.
/// - Parameters:
/// - identifier: The identifier of the constraint to find.
/// - constant: The new constant value to set. Defaults to nil.
/// - priority: The new priority to set. Defaults to nil.
func updateConstraint(withIdentifier identifier: AnchorType, constant: CGFloat? = nil, priority: UILayoutPriority? = nil) {
updateConstraint(withIdentifier: identifier.rawValue, constant: constant, priority: priority)
}
}