Skip to content

Commit

Permalink
Added stackview helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
cjnevin committed Oct 15, 2022
1 parent fe9c729 commit beafbdc
Show file tree
Hide file tree
Showing 3 changed files with 291 additions and 0 deletions.
184 changes: 184 additions & 0 deletions Sources/AutoLayoutBuilder/StackView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
#if canImport(UIKit)

import UIKit

public protocol StackViewProtocol: UIView {
@discardableResult func addStackedView(_ view: StackableView) -> Self
@discardableResult func insertStackedView(_ view: StackableView, at index: Int) -> Self
@discardableResult func insertStackedView(_ view: StackableView, after: StackableView) -> Self
@discardableResult func removeStackedView(_ view: StackableView) -> Self
@discardableResult func hideStackedView(_ view: StackableView) -> Self
@discardableResult func showStackedView(_ view: StackableView) -> Self
@discardableResult func replaceStackedViews(@StackableViewBuilder _ views: () -> [StackableView]) -> Self
@discardableResult func spacing(_ spacing: CGFloat) -> Self
@discardableResult func spacing(_ spacing: CGFloat, after: StackableView) -> Self
}

public class StackView: UIView, StackViewProtocol {
private let stackView = UIStackView()
private var stackedViews: [StackableView]

public init(axis: NSLayoutConstraint.Axis = .vertical, @StackableViewBuilder subviews: () -> [StackableView] = { [] }) {
stackedViews = subviews()
super.init(frame: .zero)
stackedViews
.lazy
.map(\.view)
.forEach(stackView.addArrangedSubview)
stackView.axis = axis
addSubview(stackView) {
$0.edges == Superview()
}
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

@discardableResult public func addStackedView(_ view: StackableView) -> Self {
stackedViews.append(view)
stackView.addArrangedSubview(view.view)
return self
}

@discardableResult public func insertStackedView(_ view: StackableView, at index: Int) -> Self {
stackedViews.insert(view, at: index + 1)
stackView.insertArrangedSubview(view.view, at: index + 1)
return self
}

@discardableResult public func insertStackedView(_ view: StackableView, after: StackableView) -> Self {
stackedIndex(for: after.view).map { index in
insertStackedView(view, at: index)
} ?? self
}

@discardableResult public func removeStackedView(_ view: StackableView) -> Self {
stackedIndex(for: view).map { index in
stackedViews[index].view.removeFromSuperview()
stackView.removeArrangedSubview(stackedViews[index].view)
stackedViews.remove(at: index)
}
return self
}

@discardableResult public func hideStackedView(_ view: StackableView) -> Self {
stackedView(for: view)?.view.isHidden = true
return self
}

@discardableResult public func showStackedView(_ view: StackableView) -> Self {
stackedView(for: view)?.view.isHidden = false
return self
}

@discardableResult public func replaceStackedViews(@StackableViewBuilder _ views: () -> [StackableView]) -> Self {
stackedViews.forEach {
removeStackedView($0)
}
views().forEach {
addStackedView($0)
}
return self
}

@discardableResult public func spacing(_ spacing: CGFloat) -> Self {
stackView.spacing = spacing
return self
}

@discardableResult public func spacing(_ spacing: CGFloat, after: StackableView) -> Self {
(stackedView(for: after)?.view).map {
stackView.setCustomSpacing(spacing, after: $0)
}
return self
}

private func stackedIndex(for view: StackableView) -> Int? {
stackedViews.firstIndex { stackableView in
stackableView.view == view.originalView
|| stackableView.originalView == view.originalView
|| stackableView.view == view.view
|| stackableView.originalView == view.view
}
}

private func stackedView(for view: StackableView) -> StackableView? {
stackedIndex(for: view).map {
stackedViews[$0]
}
}
}

public class ScrollableStackView: UIView, StackViewProtocol {
private let stackView: StackView
private let scrollView: UIScrollView

public init(axis: NSLayoutConstraint.Axis = .vertical, @StackableViewBuilder subviews: () -> [StackableView] = { [] }) {
scrollView = UIScrollView()
stackView = StackView(axis: axis, subviews: subviews)
super.init(frame: .zero)
addSubview(scrollView) {
$0.edges == Superview()
$0.addSubview(stackView) {
if axis == .vertical {
$0.width == self
} else {
$0.height == self
}
$0.edges == Superview()
}
}
}

@discardableResult public func addStackedView(_ view: StackableView) -> Self {
stackView.addStackedView(view.view)
return self
}

@discardableResult public func insertStackedView(_ view: StackableView, at index: Int) -> Self {
stackView.insertStackedView(view, at: index)
return self
}

@discardableResult public func insertStackedView(_ view: StackableView, after: StackableView) -> Self {
stackView.insertStackedView(view, after: after)
return self
}

@discardableResult public func removeStackedView(_ view: StackableView) -> Self {
stackView.removeStackedView(view)
return self
}

@discardableResult public func hideStackedView(_ view: StackableView) -> Self {
stackView.hideStackedView(view)
return self
}

@discardableResult public func showStackedView(_ view: StackableView) -> Self {
stackView.showStackedView(view)
return self
}

@discardableResult public func replaceStackedViews(@StackableViewBuilder _ views: () -> [StackableView]) -> Self {
stackView.replaceStackedViews(views)
return self
}

@discardableResult public func spacing(_ spacing: CGFloat) -> Self {
stackView.spacing(spacing)
return self
}

@discardableResult public func spacing(_ spacing: CGFloat, after: StackableView) -> Self {
stackView.spacing(spacing, after: after)
return self
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

#endif
75 changes: 75 additions & 0 deletions Sources/AutoLayoutBuilder/StackableView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#if canImport(UIKit)

import UIKit

public protocol StackableView {
var view: UIView { get }
var originalView: UIView { get }
}

extension UIView: StackableView {
public var view: UIView { self }
public var originalView: UIView { self }
}

extension StackableView {
public func top(_ padding: CGFloat = 0) -> StackableView {
StackedView(view, originalView: originalView) {
$0.horizontalEdges.top(padding) == Superview()
$0.bottom <= Superview()
}
}

public func leading(_ padding: CGFloat = 0) -> StackableView {
StackedView(view, originalView: originalView) {
$0.verticalEdges.leading(padding) == Superview()
$0.trailing <= Superview()
}
}

public func trailing(_ padding: CGFloat = 0) -> StackableView {
StackedView(view, originalView: originalView) {
$0.verticalEdges.trailing(-padding) == Superview()
$0.leading >= Superview()
}
}

public func bottom(_ padding: CGFloat = 0) -> StackableView {
StackedView(view, originalView: originalView) {
$0.horizontalEdges.bottom(-padding) == Superview()
$0.top >= Superview()
}
}

public func centered(_ padding: CGFloat = 0) -> StackableView {
StackedView(view, originalView: originalView) {
$0.centerX.centerY == Superview()
$0.top(padding).leading(padding) >= Superview()
$0.trailing(-padding).bottom(-padding) <= Superview()
}
}

public func padding(_ padding: CGFloat) -> StackableView {
StackedView(view, originalView: originalView) {
$0.horizontalEdges(padding).verticalEdges == Superview()
}
}

public func verticalPadding(_ padding: CGFloat) -> StackableView {
StackedView(view, originalView: originalView) {
$0.horizontalEdges.verticalEdges(padding) == Superview()
}
}
}

private struct StackedView: StackableView {
let view: UIView = UIView()
let originalView: UIView

init<View: UIView>(_ targetView: View, originalView: UIView, @AutoLayoutBuilder constraints: (View) -> [Constrainable]) {
self.originalView = originalView
self.view.addSubview(targetView, with: constraints)
}
}

#endif
32 changes: 32 additions & 0 deletions Sources/AutoLayoutBuilder/StackableViewBuilder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#if canImport(UIKit)

import UIKit

@resultBuilder
public enum StackableViewBuilder {
public static func buildBlock(_ components: StackableView...) -> [StackableView] {
components
}

public static func buildArray(_ components: [[StackableView]]) -> [StackableView] {
components.flatMap { $0 }
}

public static func buildOptional(_ component: [StackableView]?) -> [StackableView] {
component ?? []
}

public static func buildEither(first component: [StackableView]) -> [StackableView] {
component
}

public static func buildEither(second component: [StackableView]) -> [StackableView] {
component
}

public static func buildLimitedAvailability(_ component: [StackableView]) -> [StackableView] {
component
}
}

#endif

0 comments on commit beafbdc

Please sign in to comment.