From ad082723d6354017a00dfd8f236d9f72ee37aaa4 Mon Sep 17 00:00:00 2001 From: Selim Mustafaev Date: Fri, 28 Jul 2023 21:23:18 +0300 Subject: [PATCH] Improving Stack (adding orientation and spacing) --- .../UIView/UIView+Constraints.swift | 44 +++++++ .../UIView+Fluent.swift} | 13 +- .../YadUI/Views/Stack/Stack+Directional.swift | 46 +++++++ Sources/YadUI/Views/Stack/Stack+Enums.swift | 27 ++++ Sources/YadUI/Views/Stack/Stack.swift | 106 ++++++++++++++++ Sources/YadUI/Views/VStack.swift | 115 ------------------ YadUIDemo/YadUIDemo/ViewController.swift | 6 +- 7 files changed, 229 insertions(+), 128 deletions(-) create mode 100644 Sources/YadUI/Extensions/UIView/UIView+Constraints.swift rename Sources/YadUI/Extensions/{UIView.swift => UIView/UIView+Fluent.swift} (51%) create mode 100644 Sources/YadUI/Views/Stack/Stack+Directional.swift create mode 100644 Sources/YadUI/Views/Stack/Stack+Enums.swift create mode 100644 Sources/YadUI/Views/Stack/Stack.swift delete mode 100644 Sources/YadUI/Views/VStack.swift diff --git a/Sources/YadUI/Extensions/UIView/UIView+Constraints.swift b/Sources/YadUI/Extensions/UIView/UIView+Constraints.swift new file mode 100644 index 0000000..58af749 --- /dev/null +++ b/Sources/YadUI/Extensions/UIView/UIView+Constraints.swift @@ -0,0 +1,44 @@ +// +// UIView+Constraints.swift +// +// +// Created by Selim Mustafaev on 28.07.2023. +// + +import UIKit + +extension UIView { + + public func pin(to view: UIView, insets: UIEdgeInsets = .zero) { + NSLayoutConstraint.activate([ + leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: insets.left), + trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -insets.right), + topAnchor.constraint(equalTo: view.topAnchor, constant: insets.top), + bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -insets.bottom) + ]) + } + + public func pin(toSafeArea view: UIView, insets: UIEdgeInsets = .zero) { + NSLayoutConstraint.activate([ + leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: insets.left), + trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -insets.right), + topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: insets.top), + bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -insets.bottom) + ]) + } + + public func constraint(src: UIView, + srcAttr: NSLayoutConstraint.Attribute, + dst: UIView, + dstAttr: NSLayoutConstraint.Attribute, + constant: CGFloat = 0) { + + addConstraint(.init(item: src, + attribute: srcAttr, + relatedBy: .equal, + toItem: dst, + attribute: dstAttr, + multiplier: 1.0, + constant: constant)) + } +} diff --git a/Sources/YadUI/Extensions/UIView.swift b/Sources/YadUI/Extensions/UIView/UIView+Fluent.swift similarity index 51% rename from Sources/YadUI/Extensions/UIView.swift rename to Sources/YadUI/Extensions/UIView/UIView+Fluent.swift index 3eabf7f..54af469 100644 --- a/Sources/YadUI/Extensions/UIView.swift +++ b/Sources/YadUI/Extensions/UIView/UIView+Fluent.swift @@ -1,8 +1,8 @@ // -// UIView.swift +// UIView+Fluent.swift // // -// Created by Мустафаев Селим Мустафаевич on 27.07.2023. +// Created by Selim Mustafaev on 28.07.2023. // import UIKit @@ -14,15 +14,6 @@ extension UIView { return self } - public func pin(to view: UIView, insets: UIEdgeInsets = .zero) { - NSLayoutConstraint.activate([ - leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: insets.left), - trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -insets.right), - topAnchor.constraint(equalTo: view.topAnchor, constant: insets.top), - bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -insets.bottom) - ]) - } - public func width(_ width: CGFloat) -> Self { widthAnchor.constraint(equalToConstant: width).isActive = true return self diff --git a/Sources/YadUI/Views/Stack/Stack+Directional.swift b/Sources/YadUI/Views/Stack/Stack+Directional.swift new file mode 100644 index 0000000..a49fab6 --- /dev/null +++ b/Sources/YadUI/Views/Stack/Stack+Directional.swift @@ -0,0 +1,46 @@ +// +// Stack+Directional.swift +// +// +// Created by Selim Mustafaev on 28.07.2023. +// + +import UIKit + +public class VStack: Stack { + + public init(alignment: Alignment = .fill, + distribution: Distribution = .fill, + spacing: CGFloat = 0, + @StackViewBuilder _ content: () -> [UIView]) { + + super.init(axis: .vertical, + alignment: alignment, + distribution: distribution, + spacing: spacing, + content) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +public class HStack: Stack { + + public init(alignment: Alignment = .fill, + distribution: Distribution = .fill, + spacing: CGFloat = 0, + @StackViewBuilder _ content: () -> [UIView]) { + + super.init(axis: .horizontal, + alignment: alignment, + distribution: distribution, + spacing: spacing, + content) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Sources/YadUI/Views/Stack/Stack+Enums.swift b/Sources/YadUI/Views/Stack/Stack+Enums.swift new file mode 100644 index 0000000..cddaef6 --- /dev/null +++ b/Sources/YadUI/Views/Stack/Stack+Enums.swift @@ -0,0 +1,27 @@ +// +// Stack+Enums.swift +// +// +// Created by Selim Mustafaev on 28.07.2023. +// + +import UIKit + +extension Stack { + + public enum Alignment { + + case fill + } + + public enum Distribution { + + case fill + } + + public enum Axis { + + case horizontal + case vertical + } +} diff --git a/Sources/YadUI/Views/Stack/Stack.swift b/Sources/YadUI/Views/Stack/Stack.swift new file mode 100644 index 0000000..21ebac2 --- /dev/null +++ b/Sources/YadUI/Views/Stack/Stack.swift @@ -0,0 +1,106 @@ +// +// Stack.swift +// +// +// Created by Мустафаев Селим Мустафаевич on 27.07.2023. +// + +import UIKit + +public class Stack: UIView { + + private var arrangedSubviews: [UIView] + private var alignment: Alignment + private var distribution: Distribution + private var axis: Axis + private var spacing: CGFloat + + var aStart: NSLayoutConstraint.Attribute { + axis == .vertical ? .leading : .top + } + + var aEnd: NSLayoutConstraint.Attribute { + axis == .vertical ? .trailing : .bottom + } + + var dStart: NSLayoutConstraint.Attribute { + axis == .vertical ? .top : .leading + } + + var dEnd: NSLayoutConstraint.Attribute { + axis == .vertical ? .bottom : .trailing + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public init(axis: Axis = .vertical, + alignment: Alignment = .fill, + distribution: Distribution = .fill, + spacing: CGFloat = 0, + @StackViewBuilder _ content: () -> [UIView]) { + + self.axis = axis + self.alignment = alignment + self.distribution = distribution + self.spacing = spacing + self.arrangedSubviews = content() + super.init(frame: .zero) + + self.arrangedSubviews.forEach(addSubview) + addAlignmentConstraints() + addDistributionConstraints() + } + + func addAlignmentConstraints() { + + switch alignment { + case .fill: + addAlignmentFillConstraints() + } + } + + func addDistributionConstraints() { + + switch distribution { + case .fill: + addDistributionFillConstraints() + } + } + + func addAlignmentFillConstraints() { + guard let firstView = arrangedSubviews.first else { return } + + let otherViews = arrangedSubviews.dropFirst() + + constraint(src: firstView, srcAttr: aStart, dst: self, dstAttr: aStart) + constraint(src: firstView, srcAttr: aEnd, dst: self, dstAttr: aEnd) + + for view in otherViews { + constraint(src: view, srcAttr: aStart, dst: firstView, dstAttr: aStart) + constraint(src: view, srcAttr: aEnd, dst: firstView, dstAttr: aEnd) + } + } + + func addDistributionFillConstraints() { + guard let firstView = arrangedSubviews.first, + let lastView = arrangedSubviews.last + else { + return + } + + let middleViews = arrangedSubviews.dropFirst().dropLast() + + constraint(src: firstView, srcAttr: dStart, dst: self, dstAttr: dStart) + constraint(src: lastView, srcAttr: dEnd, dst: self, dstAttr: dEnd) + + var currentView = firstView + for view in middleViews { + constraint(src: view, srcAttr: dStart, dst: currentView, dstAttr: dEnd, constant: spacing) + currentView = view + } + + constraint(src: lastView, srcAttr: dStart, dst: currentView, dstAttr: dEnd, constant: spacing) + } +} diff --git a/Sources/YadUI/Views/VStack.swift b/Sources/YadUI/Views/VStack.swift deleted file mode 100644 index c18e89d..0000000 --- a/Sources/YadUI/Views/VStack.swift +++ /dev/null @@ -1,115 +0,0 @@ -// -// VStack.swift -// -// -// Created by Мустафаев Селим Мустафаевич on 27.07.2023. -// - -import UIKit - -public enum StackAlignment { - - case fill -} - -public enum StackDistribution { - - case fill -} - -public enum StackAxis { - - case horizontal - case vertical -} - -public final class VStack: UIView { - - private var arrangedSubviews: [UIView] - private var alignment: StackAlignment - private var distribution: StackDistribution - private var axis: StackAxis - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - public init(axis: StackAxis = .vertical, - alignment: StackAlignment = .fill, - distribution: StackDistribution = .fill, - @StackViewBuilder _ content: () -> [UIView]) { - - self.axis = axis - self.alignment = alignment - self.distribution = distribution - self.arrangedSubviews = content() - super.init(frame: .zero) - - self.arrangedSubviews.forEach(addSubview) - NSLayoutConstraint.activate(generateAlignmentConstraints()) - NSLayoutConstraint.activate(generateDistributionConstraints()) - } - - func generateAlignmentConstraints() -> [NSLayoutConstraint] { - - switch alignment { - case .fill: - return generateAlignmentFillConstraints() - } - } - - func generateDistributionConstraints() -> [NSLayoutConstraint] { - - switch distribution { - case .fill: - return generateDistributionFillConstraints() - } - } - - func generateAlignmentFillConstraints() -> [NSLayoutConstraint] { - guard let firstView = arrangedSubviews.first else { - return [] - } - - let otherViews = arrangedSubviews.dropFirst() - - var constraints: [NSLayoutConstraint] = [ - firstView.leadingAnchor.constraint(equalTo: leadingAnchor), - firstView.trailingAnchor.constraint(equalTo: trailingAnchor) - ] - - for view in otherViews { - constraints.append(contentsOf: [ - view.leadingAnchor.constraint(equalTo: firstView.leadingAnchor), - view.trailingAnchor.constraint(equalTo: firstView.trailingAnchor) - ]) - } - - return constraints - } - - func generateDistributionFillConstraints() -> [NSLayoutConstraint] { - guard let firstView = arrangedSubviews.first, - let lastView = arrangedSubviews.last - else { - return [] - } - - let middleViews = arrangedSubviews.dropFirst().dropLast() - - var constraints: [NSLayoutConstraint] = [ - firstView.topAnchor.constraint(equalTo: topAnchor), - lastView.bottomAnchor.constraint(equalTo: bottomAnchor) - ] - - var currentView = firstView - for view in middleViews { - constraints.append(view.topAnchor.constraint(equalTo: currentView.bottomAnchor)) - currentView = view - } - - constraints.append(lastView.topAnchor.constraint(equalTo: currentView.bottomAnchor)) - - return constraints - } -} diff --git a/YadUIDemo/YadUIDemo/ViewController.swift b/YadUIDemo/YadUIDemo/ViewController.swift index abf737c..3ae00e9 100644 --- a/YadUIDemo/YadUIDemo/ViewController.swift +++ b/YadUIDemo/YadUIDemo/ViewController.swift @@ -15,16 +15,18 @@ class ViewController: UIViewController { let child = buildView() view.addSubview(child) - child.pin(to: view) + child.pin(toSafeArea: view) } @RootViewBuilder func buildView() -> UIView { - VStack { + VStack(spacing: 16) { UIView() .background(.red) + //.width(100) .height(100) UIView() .background(.green) + //.width(100) .height(100) UIView() .background(.blue)