-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
291 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |