added container
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
This commit is contained in:
parent
01a177f643
commit
5ebc7408f5
@ -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
636
VDS/Classes/Container.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user