Improving Stack (adding orientation and spacing)

This commit is contained in:
Selim Mustafaev 2023-07-28 21:23:18 +03:00
parent 03999cb555
commit ad082723d6
7 changed files with 229 additions and 128 deletions

View File

@ -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))
}
}

View File

@ -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

View File

@ -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")
}
}

View File

@ -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
}
}

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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)