mirror of
https://github.com/tappollo/makata.git
synced 2026-04-30 04:45:07 +08:00
Code cleanup
This commit is contained in:
@@ -7,14 +7,14 @@ import Foundation
|
||||
|
||||
public struct FieldValidator<Shape, Value> {
|
||||
public let validate: (Shape, Value) throws -> Void
|
||||
|
||||
|
||||
public let propagates: Bool
|
||||
|
||||
public init(propagates: Bool = true, validate: @escaping (Shape, Value) throws -> Void) {
|
||||
self.validate = validate
|
||||
self.propagates = propagates
|
||||
}
|
||||
|
||||
|
||||
public init(propagates: Bool = true, validate: @escaping (Value) throws -> Void) {
|
||||
self.validate = { _, value throws in
|
||||
try validate(value)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// form-binding.swift
|
||||
//
|
||||
// Code Copyright Buslo Collective
|
||||
// Created 2/3/23
|
||||
// Created 2/23/23
|
||||
|
||||
import Foundation
|
||||
|
||||
@@ -22,21 +22,6 @@ public struct Binding<Source: AnyObject, Value> {
|
||||
initialValue = source[keyPath: path]
|
||||
}
|
||||
|
||||
#if swift(<5.8) // issue fixed in swift 5.8!
|
||||
public init(
|
||||
source: Source,
|
||||
to path: ReferenceWritableKeyPath<Source, Value?>
|
||||
) {
|
||||
action = { [unowned source] value in
|
||||
source[keyPath: path] = value
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
initialValue = source[keyPath: path]
|
||||
}
|
||||
#endif
|
||||
|
||||
public init<Out, T: FieldTransformable>(
|
||||
source: Source,
|
||||
to path: ReferenceWritableKeyPath<Source, Out>,
|
||||
@@ -148,6 +133,23 @@ public extension Binding {
|
||||
}
|
||||
}
|
||||
|
||||
public extension Binding {
|
||||
#if swift(<5.8)
|
||||
init(
|
||||
source: Source,
|
||||
to path: ReferenceWritableKeyPath<Source, Value?>
|
||||
) {
|
||||
action = { [unowned source] value in
|
||||
source[keyPath: path] = value
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
initialValue = source[keyPath: path]
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public typealias FieldPartialValueKeyPath<Source, Complete, Value> = ReferenceWritableKeyPath<
|
||||
Source, FieldPartialValue<Complete, Value>
|
||||
>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// form.swift
|
||||
// form-handler.swift
|
||||
//
|
||||
// Code Copyright Buslo Collective
|
||||
// Created 2/3/23
|
||||
@@ -18,7 +18,7 @@ public class FormHandler<Shape> {
|
||||
public var isSubmitFailed: Bool {
|
||||
submitErrors != nil
|
||||
}
|
||||
|
||||
|
||||
public let submitErrors: Error?
|
||||
|
||||
public let validationResult: FormValidation<Shape>.Result
|
||||
@@ -30,11 +30,11 @@ public class FormHandler<Shape> {
|
||||
|
||||
var submitInvoked: Bool
|
||||
var submitErrors: Error?
|
||||
|
||||
|
||||
var updateHandler: UpdatesHandler = { _, _ async in }
|
||||
|
||||
var validations: FormValidation<Shape>?
|
||||
|
||||
|
||||
var observations: FormObserver<Shape>?
|
||||
|
||||
public init(initial: Shape, submitInvoked: Bool = false) {
|
||||
@@ -57,7 +57,7 @@ public class FormHandler<Shape> {
|
||||
} catch {
|
||||
submitErrors = error
|
||||
await pushUpdates()
|
||||
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -74,7 +74,7 @@ public class FormHandler<Shape> {
|
||||
validationResult: result
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -97,11 +97,11 @@ public extension FormHandler {
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
|
||||
@discardableResult
|
||||
func setObserverHandler(_ handler: FormObserver<Shape>?) -> Self {
|
||||
observations = handler
|
||||
|
||||
|
||||
return self
|
||||
}
|
||||
}
|
||||
@@ -113,11 +113,11 @@ public extension FormHandler {
|
||||
}
|
||||
set {
|
||||
current[keyPath: member] = newValue
|
||||
|
||||
|
||||
if let observations, let action = observations.observationDict[member] {
|
||||
action(newValue)
|
||||
}
|
||||
|
||||
|
||||
Task { @MainActor () in await pushUpdates() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
// form-observer.swift
|
||||
//
|
||||
// File.swift
|
||||
//
|
||||
//
|
||||
// Created by Michael Ong on 4/5/23.
|
||||
//
|
||||
// Code Copyright Buslo Collective
|
||||
// Created 4/5/23
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct FormObserver<Shape> {
|
||||
let observationDict: [PartialKeyPath<Shape> : (Any) -> Void]
|
||||
let observationDict: [PartialKeyPath<Shape>: (Any) -> Void]
|
||||
|
||||
public init() {
|
||||
observationDict = [:]
|
||||
}
|
||||
|
||||
init(observationDict: [PartialKeyPath<Shape> : (Any) -> Void]) {
|
||||
init(observationDict: [PartialKeyPath<Shape>: (Any) -> Void]) {
|
||||
self.observationDict = observationDict
|
||||
}
|
||||
|
||||
@@ -23,7 +21,7 @@ public struct FormObserver<Shape> {
|
||||
newDict.updateValue({ value in
|
||||
observer(value as! Value)
|
||||
}, forKey: path)
|
||||
|
||||
|
||||
return .init(observationDict: newDict)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,23 +29,23 @@ public struct FormValidation<Shape> {
|
||||
if validationsDict[path] != nil {
|
||||
fatalError("Appending additional validations for path not allowed.")
|
||||
}
|
||||
|
||||
|
||||
var newDict = validationsDict
|
||||
newDict[path] = { shape in
|
||||
var fieldErrors = [Error]()
|
||||
|
||||
|
||||
for field in fields {
|
||||
do {
|
||||
try field.validate(shape, shape[keyPath: path])
|
||||
} catch {
|
||||
fieldErrors.append(error)
|
||||
|
||||
|
||||
if !field.propagates {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return fieldErrors
|
||||
}
|
||||
|
||||
@@ -94,63 +94,63 @@ public struct FormValidation<Shape> {
|
||||
}
|
||||
}
|
||||
|
||||
extension FormValidation {
|
||||
#if swift(<5.8)
|
||||
public func validations<Value>(
|
||||
for path: KeyPath<Shape, Value?>,
|
||||
are fields: FieldValidator<Shape, Value>...
|
||||
) -> Self {
|
||||
var newDict = validationsDict
|
||||
newDict[path] = { shape in
|
||||
var fieldErrors = [Error]()
|
||||
public extension FormValidation {
|
||||
#if swift(<5.8)
|
||||
func validations<Value>(
|
||||
for path: KeyPath<Shape, Value?>,
|
||||
are fields: FieldValidator<Shape, Value>...
|
||||
) -> Self {
|
||||
var newDict = validationsDict
|
||||
newDict[path] = { shape in
|
||||
var fieldErrors = [Error]()
|
||||
|
||||
fields.forEach { field in
|
||||
do {
|
||||
if let value = shape[keyPath: path] {
|
||||
try field.validate(shape, value)
|
||||
} else {
|
||||
fatalError("Tried to validate a field that does not exist or is nil.")
|
||||
}
|
||||
} catch {
|
||||
fieldErrors.append(error)
|
||||
}
|
||||
}
|
||||
|
||||
return fieldErrors
|
||||
}
|
||||
|
||||
return .init(newDict)
|
||||
}
|
||||
|
||||
public func validations<Value>(
|
||||
for path: KeyPath<Shape, FieldPartialValue<Value, some Any>?>,
|
||||
are fields: FieldValidator<Shape, Value>...
|
||||
) -> Self {
|
||||
var newDict = validationsDict
|
||||
newDict[path] = { shape in
|
||||
var fieldErrors = [Error]()
|
||||
|
||||
fields.forEach { field in
|
||||
do {
|
||||
if let value = shape[keyPath: path] {
|
||||
switch value {
|
||||
case let .complete(complete):
|
||||
try field.validate(shape, complete)
|
||||
case let .partial(_, error):
|
||||
throw FieldError.incomplete(error)
|
||||
fields.forEach { field in
|
||||
do {
|
||||
if let value = shape[keyPath: path] {
|
||||
try field.validate(shape, value)
|
||||
} else {
|
||||
fatalError("Tried to validate a field that does not exist or is nil.")
|
||||
}
|
||||
} else {
|
||||
fatalError("Tried to validate a field that does not exist or is nil.")
|
||||
} catch {
|
||||
fieldErrors.append(error)
|
||||
}
|
||||
} catch {
|
||||
fieldErrors.append(error)
|
||||
}
|
||||
|
||||
return fieldErrors
|
||||
}
|
||||
|
||||
return fieldErrors
|
||||
return .init(newDict)
|
||||
}
|
||||
|
||||
return .init(newDict)
|
||||
}
|
||||
#endif
|
||||
func validations<Value>(
|
||||
for path: KeyPath<Shape, FieldPartialValue<Value, some Any>?>,
|
||||
are fields: FieldValidator<Shape, Value>...
|
||||
) -> Self {
|
||||
var newDict = validationsDict
|
||||
newDict[path] = { shape in
|
||||
var fieldErrors = [Error]()
|
||||
|
||||
fields.forEach { field in
|
||||
do {
|
||||
if let value = shape[keyPath: path] {
|
||||
switch value {
|
||||
case let .complete(complete):
|
||||
try field.validate(shape, complete)
|
||||
case let .partial(_, error):
|
||||
throw FieldError.incomplete(error)
|
||||
}
|
||||
} else {
|
||||
fatalError("Tried to validate a field that does not exist or is nil.")
|
||||
}
|
||||
} catch {
|
||||
fieldErrors.append(error)
|
||||
}
|
||||
}
|
||||
|
||||
return fieldErrors
|
||||
}
|
||||
|
||||
return .init(newDict)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// protocol-field-transformable.swift
|
||||
//
|
||||
// Code Copyright Buslo Collective
|
||||
// Created 2/3/23
|
||||
// Created 2/23/23
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
@@ -10,24 +10,22 @@ public protocol CollectionDelegate: AnyObject {
|
||||
associatedtype DelegateItemType: Hashable
|
||||
|
||||
@MainActor func collectionItemSelected(at indexPath: IndexPath, _ item: DelegateItemType) async
|
||||
|
||||
|
||||
@MainActor func collectionContentRectChanged(_ rect: Templates.CollectionContentRect)
|
||||
}
|
||||
|
||||
public extension CollectionDelegate {
|
||||
func collectionContentRectChanged(_ rect: Templates.CollectionContentRect) {
|
||||
|
||||
}
|
||||
func collectionContentRectChanged(_: Templates.CollectionContentRect) {}
|
||||
}
|
||||
|
||||
public extension Templates {
|
||||
struct CollectionContentRect {
|
||||
public let offset: CGPoint
|
||||
public let offsetAdjusted: CGPoint
|
||||
|
||||
|
||||
public let size: CGSize
|
||||
}
|
||||
|
||||
|
||||
final class Collection<S: Hashable, E: Hashable>: UIView, HasHeader {
|
||||
public typealias SectionLayout = (__shared DataSource, Int, S) -> NSCollectionLayoutSection
|
||||
|
||||
@@ -38,11 +36,14 @@ public extension Templates {
|
||||
public private(set) weak var headerView: (UIView & ViewHeader)?
|
||||
|
||||
public private(set) weak var collectionView: UICollectionView!
|
||||
|
||||
|
||||
public var showHairlineBorder = true {
|
||||
didSet {
|
||||
let invalidationContext = UICollectionViewLayoutInvalidationContext()
|
||||
invalidationContext.invalidateSupplementaryElements(ofKind: CollectionHeaderElementType, at: [ .init(row: 0, section: 0) ])
|
||||
invalidationContext.invalidateSupplementaryElements(
|
||||
ofKind: CollectionHeaderElementType,
|
||||
at: [.init(row: 0, section: 0)]
|
||||
)
|
||||
|
||||
collectionView.collectionViewLayout.invalidateLayout(with: invalidationContext)
|
||||
}
|
||||
@@ -52,13 +53,13 @@ public extension Templates {
|
||||
|
||||
public init(
|
||||
frame: CGRect,
|
||||
header: __owned (UIView & ViewHeader)? = nil,
|
||||
header: __owned(UIView & ViewHeader)? = nil,
|
||||
footer: __owned UIView? = nil,
|
||||
source: (__shared UICollectionView) -> DataSource,
|
||||
layout: (_ dataSource: DataSource) -> UICollectionViewCompositionalLayout
|
||||
) {
|
||||
headerView = header
|
||||
|
||||
|
||||
let headerRegistration = Header.Registration
|
||||
let footerRegistration = Footer.Registration
|
||||
|
||||
@@ -96,10 +97,11 @@ public extension Templates {
|
||||
}
|
||||
}
|
||||
|
||||
addSubview(view: collectionView) { make in
|
||||
make.edges
|
||||
.equalToSuperview()
|
||||
}
|
||||
addSubview(collectionView
|
||||
.defineConstraints { make in
|
||||
make.edges
|
||||
.equalToSuperview()
|
||||
})
|
||||
|
||||
let collectionLayout = layout(source)
|
||||
let configuration = collectionLayout.configuration
|
||||
@@ -143,7 +145,7 @@ public extension Templates {
|
||||
|
||||
public convenience init(
|
||||
frame: CGRect,
|
||||
header: __owned (UIView & ViewHeader)? = nil,
|
||||
header: __owned(UIView & ViewHeader)? = nil,
|
||||
footer: __owned UIView? = nil,
|
||||
source: (__shared UICollectionView) -> DataSource,
|
||||
layout: @escaping SectionLayout
|
||||
@@ -152,15 +154,16 @@ public extension Templates {
|
||||
frame: frame,
|
||||
header: header,
|
||||
footer: footer,
|
||||
source: source) { source in
|
||||
UICollectionViewCompositionalLayout(sectionProvider: { [unowned source] section, _ in
|
||||
guard let sectionKind = source.sectionIdentifier(for: section) else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
return layout(source, section, sectionKind)
|
||||
})
|
||||
}
|
||||
source: source
|
||||
) { source in
|
||||
UICollectionViewCompositionalLayout(sectionProvider: { [unowned source] section, _ in
|
||||
guard let sectionKind = source.sectionIdentifier(for: section) else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
return layout(source, section, sectionKind)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
@@ -248,39 +251,42 @@ extension Templates.Collection {
|
||||
|
||||
func setContainingView(_ content: UIView) -> Self {
|
||||
addSubview(
|
||||
view: UIVisualEffectView(
|
||||
effect: UIBlurEffect(style: .regular)
|
||||
)
|
||||
.assign(to: &visualEffect)
|
||||
.hidden()
|
||||
) { make in
|
||||
make.leading
|
||||
.bottom
|
||||
.trailing
|
||||
.equalToSuperview()
|
||||
make.top
|
||||
.equalToSuperview()
|
||||
.inset(-300)
|
||||
}
|
||||
|
||||
addSubview(view: content) { make in
|
||||
make.edges
|
||||
.equalToSuperview()
|
||||
}
|
||||
UIVisualEffectView(effect: UIBlurEffect(style: .regular))
|
||||
.assign(to: &visualEffect)
|
||||
.hidden()
|
||||
.defineConstraints { make in
|
||||
make.leading
|
||||
.bottom
|
||||
.trailing
|
||||
.equalToSuperview()
|
||||
make.top
|
||||
.equalToSuperview()
|
||||
.inset(-300)
|
||||
}
|
||||
)
|
||||
|
||||
addSubview(
|
||||
view: UIView()
|
||||
content
|
||||
.defineConstraints { make in
|
||||
make.edges
|
||||
.equalToSuperview()
|
||||
}
|
||||
)
|
||||
|
||||
addSubview(
|
||||
UIView()
|
||||
.backgroundColor(.separator)
|
||||
.hidden()
|
||||
.assign(to: &border)
|
||||
) { make in
|
||||
make.horizontalEdges
|
||||
.bottom
|
||||
.equalToSuperview()
|
||||
.defineConstraints { make in
|
||||
make.horizontalEdges
|
||||
.bottom
|
||||
.equalToSuperview()
|
||||
|
||||
make.height
|
||||
.equalTo(1 / UIScreen.main.scale)
|
||||
}
|
||||
make.height
|
||||
.equalTo(1 / UIScreen.main.scale)
|
||||
}
|
||||
)
|
||||
|
||||
traitCollectionDidChange(nil)
|
||||
|
||||
@@ -325,25 +331,27 @@ extension Templates.Collection {
|
||||
|
||||
func setContainingView(_ content: UIView) -> Self {
|
||||
addSubview(
|
||||
view: UIVisualEffectView(
|
||||
effect: UIBlurEffect(style: .regular)
|
||||
)
|
||||
.assign(to: &visualEffect)
|
||||
.hidden()
|
||||
) { make in
|
||||
make.leading
|
||||
.top
|
||||
.trailing
|
||||
.equalToSuperview()
|
||||
make.bottom
|
||||
.equalToSuperview()
|
||||
.inset(-300)
|
||||
}
|
||||
UIVisualEffectView(effect: UIBlurEffect(style: .regular))
|
||||
.assign(to: &visualEffect)
|
||||
.hidden()
|
||||
.defineConstraints { make in
|
||||
make.leading
|
||||
.top
|
||||
.trailing
|
||||
.equalToSuperview()
|
||||
make.bottom
|
||||
.equalToSuperview()
|
||||
.inset(-300)
|
||||
}
|
||||
)
|
||||
|
||||
addSubview(view: content) { make in
|
||||
make.edges
|
||||
.equalToSuperview()
|
||||
}
|
||||
addSubview(
|
||||
content
|
||||
.defineConstraints { make in
|
||||
make.edges
|
||||
.equalToSuperview()
|
||||
}
|
||||
)
|
||||
|
||||
traitCollectionDidChange(nil)
|
||||
|
||||
@@ -357,7 +365,7 @@ extension Templates.Collection {
|
||||
typealias DataSource<Section: Hashable> = UICollectionViewDiffableDataSource<Section, E>
|
||||
|
||||
var itemSelected: (IndexPath) -> Void = { _ in }
|
||||
|
||||
|
||||
var contentRectChanged: (Templates.CollectionContentRect) -> Void = { _ in }
|
||||
|
||||
func setupDelegate<D: CollectionDelegate>(
|
||||
@@ -369,7 +377,7 @@ extension Templates.Collection {
|
||||
await delegate.collectionItemSelected(at: ip, dataSource.itemIdentifier(for: ip)!)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
contentRectChanged = { [unowned delegate] rect in
|
||||
delegate.collectionContentRectChanged(rect)
|
||||
}
|
||||
@@ -391,7 +399,7 @@ extension Templates.Collection {
|
||||
header.displayUpdate(ypos >= 1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
contentRectChanged(
|
||||
.init(
|
||||
offset: scrollView.contentOffset,
|
||||
@@ -404,9 +412,12 @@ extension Templates.Collection {
|
||||
}
|
||||
|
||||
public extension Templates.CollectionContentRect {
|
||||
func interpolate(from input: ClosedRange<CGFloat>, mapsTo output: ClosedRange<CGFloat>) -> (_ value: CGFloat) -> CGFloat {
|
||||
func interpolate(
|
||||
from input: ClosedRange<CGFloat>,
|
||||
mapsTo output: ClosedRange<CGFloat>
|
||||
) -> (_ value: CGFloat) -> CGFloat {
|
||||
let length = (input.upperBound - input.lowerBound)
|
||||
|
||||
|
||||
return { value in
|
||||
let ratio = (value - input.lowerBound) / length
|
||||
return min(output.upperBound, max(output.lowerBound, output.lowerBound + output.upperBound * ratio))
|
||||
|
||||
@@ -9,23 +9,23 @@ import UIKit
|
||||
open class ControllerTemplated<Template: UIView, Hook>: Controller<Hook> {
|
||||
public lazy var screenTemplate: Template = loadTemplate(frame: parent?.view.frame ?? .zero)
|
||||
|
||||
open override var title: String? {
|
||||
override open var title: String? {
|
||||
didSet {
|
||||
guard isViewLoaded else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
updateHeader()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
open var backAction: UIAction {
|
||||
.init { [unowned self] _ in
|
||||
guard let navigationController else {
|
||||
return
|
||||
}
|
||||
|
||||
if navigationController.viewControllers.count == 1 && navigationController.topViewController == self {
|
||||
|
||||
if navigationController.viewControllers.count == 1, navigationController.topViewController == self {
|
||||
navigationController.dismiss(animated: true)
|
||||
} else {
|
||||
navigationController.popViewController(animated: true)
|
||||
@@ -33,21 +33,21 @@ open class ControllerTemplated<Template: UIView, Hook>: Controller<Hook> {
|
||||
}
|
||||
}
|
||||
|
||||
open func loadTemplate(frame: CGRect) -> Template {
|
||||
open func loadTemplate(frame _: CGRect) -> Template {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
override open func loadView() {
|
||||
view = screenTemplate
|
||||
|
||||
|
||||
updateHeader()
|
||||
}
|
||||
|
||||
|
||||
private func updateHeader() {
|
||||
if let template = screenTemplate as? HasHeader {
|
||||
template.headerView?.setupHeaderAppearance(title: title ?? "", backAction: backAction)
|
||||
}
|
||||
|
||||
|
||||
screenTemplate.setNeedsLayout()
|
||||
screenTemplate.layoutIfNeeded()
|
||||
}
|
||||
|
||||
@@ -16,8 +16,9 @@ extension UIView: ConstraintBuildable { }
|
||||
public extension ConstraintBuildable where Self: UIView {
|
||||
@discardableResult
|
||||
func addSubview(_ viewWithConstraints: ConstructedViewWithConstraints<some UIView>) -> Self {
|
||||
addSubview(view: viewWithConstraints.view, constraints: viewWithConstraints.constraint)
|
||||
|
||||
addSubview(viewWithConstraints.view)
|
||||
viewWithConstraints.view.snp.makeConstraints(viewWithConstraints.constraint)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
|
||||
@@ -12,10 +12,13 @@ open class CollectionReusableView: UICollectionReusableView, ReusableRegisterabl
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
addSubview(view: generateTemplate()) { make in
|
||||
make.edges
|
||||
.equalToSuperview()
|
||||
}
|
||||
addSubview(
|
||||
generateTemplate()
|
||||
.defineConstraints { make in
|
||||
make.edges
|
||||
.equalToSuperview()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
|
||||
@@ -11,10 +11,13 @@ open class CollectionViewCell<ItemType>: UICollectionViewCell, CellRegisterable
|
||||
super.init(frame: frame)
|
||||
|
||||
contentView
|
||||
.addSubview(view: loadView()) { make in
|
||||
make.edges
|
||||
.equalToSuperview()
|
||||
}
|
||||
.addSubview(
|
||||
loadView()
|
||||
.defineConstraints { make in
|
||||
make.edges
|
||||
.equalToSuperview()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
|
||||
173
Sources/makata-user-interface/view/view+uistackview.swift
Normal file
173
Sources/makata-user-interface/view/view+uistackview.swift
Normal file
@@ -0,0 +1,173 @@
|
||||
// view+uistackview.swift
|
||||
//
|
||||
// Code Copyright Buslo Collective
|
||||
// Created 4/6/23
|
||||
|
||||
import Foundation
|
||||
import makataInteraction
|
||||
import UIKit
|
||||
|
||||
public var DefaultStackSpacing = CGFloat(8)
|
||||
|
||||
public extension UIStackView {
|
||||
static func horizontal(
|
||||
spacing: CGFloat = DefaultStackSpacing,
|
||||
alignment: UIStackView.Alignment = .leading,
|
||||
distribution: UIStackView.Distribution = .fill,
|
||||
@ComponentBuilder components: () -> ComponentBuilder.Component
|
||||
) -> Self {
|
||||
let stack = Self(frame: .zero)
|
||||
stack.axis = .horizontal
|
||||
stack.spacing = spacing
|
||||
stack.alignment = alignment
|
||||
stack.distribution = distribution
|
||||
stack.insetsLayoutMarginsFromSafeArea = false
|
||||
|
||||
stack.setContentHuggingPriority(.required, for: .vertical)
|
||||
|
||||
switch components() {
|
||||
case let .single(view, maker):
|
||||
stack.addArrangedSubview(view)
|
||||
view.snp.makeConstraints(maker)
|
||||
|
||||
case let .result(views):
|
||||
for (view, maker) in views {
|
||||
stack.addArrangedSubview(view)
|
||||
view.snp.makeConstraints(maker)
|
||||
}
|
||||
}
|
||||
|
||||
return stack
|
||||
}
|
||||
|
||||
static func vertical(
|
||||
spacing: CGFloat = DefaultStackSpacing,
|
||||
alignment: UIStackView.Alignment = .leading,
|
||||
distribution: UIStackView.Distribution = .fill,
|
||||
@ComponentBuilder components: () -> ComponentBuilder.Component
|
||||
) -> Self {
|
||||
let stack = Self(frame: .zero)
|
||||
stack.axis = .vertical
|
||||
stack.spacing = spacing
|
||||
stack.alignment = alignment
|
||||
stack.distribution = distribution
|
||||
stack.insetsLayoutMarginsFromSafeArea = false
|
||||
|
||||
stack.setContentHuggingPriority(.required, for: .vertical)
|
||||
|
||||
switch components() {
|
||||
case let .single(view, maker):
|
||||
stack.addArrangedSubview(view)
|
||||
view.snp.makeConstraints(maker)
|
||||
|
||||
case let .result(views):
|
||||
for (view, maker) in views {
|
||||
stack.addArrangedSubview(view)
|
||||
view.snp.makeConstraints(maker)
|
||||
}
|
||||
}
|
||||
|
||||
return stack
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func margins(_ insets: UIEdgeInsets, relativeToSafeArea: Bool = false) -> Self {
|
||||
isLayoutMarginsRelativeArrangement = true
|
||||
insetsLayoutMarginsFromSafeArea = relativeToSafeArea
|
||||
layoutMargins = insets
|
||||
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
public extension UIStackView {
|
||||
static func renderHorizontal<Value>(
|
||||
from observable: Observable<Value>.Projection,
|
||||
_ lifetime: inout Lifetimeable?,
|
||||
spacing: CGFloat = DefaultStackSpacing,
|
||||
alignment: UIStackView.Alignment = .leading,
|
||||
distribution: UIStackView.Distribution = .fill,
|
||||
@ComponentBuilder components: @escaping @MainActor (Value) -> ComponentBuilder.Component
|
||||
) -> Self {
|
||||
let stack = Self(frame: .zero)
|
||||
stack.axis = .horizontal
|
||||
stack.spacing = spacing
|
||||
stack.alignment = alignment
|
||||
stack.distribution = distribution
|
||||
stack.insetsLayoutMarginsFromSafeArea = false
|
||||
|
||||
stack.setContentHuggingPriority(.required, for: .vertical)
|
||||
|
||||
var lastReferences = [UIView]()
|
||||
|
||||
lifetime = observable.subscribe { [unowned stack] value in
|
||||
DispatchQueue.main.async {
|
||||
lastReferences.forEach {
|
||||
$0.removeFromSuperview()
|
||||
}
|
||||
|
||||
switch components(value) {
|
||||
case let .single(view, maker):
|
||||
stack.addArrangedSubview(view)
|
||||
view.snp.makeConstraints(maker)
|
||||
|
||||
lastReferences = [view]
|
||||
case let .result(views):
|
||||
for (view, maker) in views {
|
||||
stack.addArrangedSubview(view)
|
||||
view.snp.makeConstraints(maker)
|
||||
}
|
||||
|
||||
lastReferences = views.map(\.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return stack
|
||||
}
|
||||
|
||||
static func renderVertical<Value>(
|
||||
from observable: Observable<Value>.Projection,
|
||||
_ lifetime: inout Lifetimeable?,
|
||||
spacing: CGFloat = DefaultStackSpacing,
|
||||
alignment: UIStackView.Alignment = .leading,
|
||||
distribution: UIStackView.Distribution = .fill,
|
||||
@ComponentBuilder components: @escaping @MainActor (Value) -> ComponentBuilder.Component
|
||||
) -> Self {
|
||||
let stack = Self(frame: .zero)
|
||||
stack.axis = .vertical
|
||||
stack.spacing = spacing
|
||||
stack.alignment = alignment
|
||||
stack.distribution = distribution
|
||||
stack.insetsLayoutMarginsFromSafeArea = false
|
||||
|
||||
stack.setContentHuggingPriority(.required, for: .vertical)
|
||||
|
||||
var lastReferences = [UIView]()
|
||||
|
||||
lifetime = observable.subscribe { [unowned stack] value in
|
||||
DispatchQueue.main.async {
|
||||
lastReferences.forEach {
|
||||
$0.removeFromSuperview()
|
||||
}
|
||||
|
||||
switch components(value) {
|
||||
case let .single(view, maker):
|
||||
stack.addArrangedSubview(view)
|
||||
view.snp.makeConstraints(maker)
|
||||
|
||||
lastReferences = [view]
|
||||
case let .result(views):
|
||||
for (view, maker) in views {
|
||||
stack.addArrangedSubview(view)
|
||||
view.snp.makeConstraints(maker)
|
||||
}
|
||||
|
||||
lastReferences = views.map(\.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return stack
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,11 @@
|
||||
// view-builder.swift
|
||||
//
|
||||
// Code Copyright Buslo Collective
|
||||
// Created 2/3/23
|
||||
// Created 3/12/23
|
||||
|
||||
//
|
||||
// File.swift
|
||||
//
|
||||
//
|
||||
// Created by Michael Ong on 2/3/23.
|
||||
//
|
||||
import Foundation
|
||||
import UIKit
|
||||
import SnapKit
|
||||
import UIKit
|
||||
|
||||
@resultBuilder
|
||||
public enum ComponentBuilder {
|
||||
@@ -21,11 +15,9 @@ public enum ComponentBuilder {
|
||||
}
|
||||
|
||||
public static func buildExpression(_ expression: some UIView) -> Component {
|
||||
.single(expression) { _ in
|
||||
|
||||
}
|
||||
.single(expression) { _ in }
|
||||
}
|
||||
|
||||
|
||||
public static func buildExpression(_ expression: ConstructedViewWithConstraints<some UIView>) -> Component {
|
||||
.single(expression.view, expression.constraint)
|
||||
}
|
||||
@@ -48,31 +40,31 @@ public enum ComponentBuilder {
|
||||
|
||||
public static func buildArray(_ components: [ComponentBuilder.Component]) -> ComponentBuilder.Component {
|
||||
var items = [(UIView, (ConstraintMaker) -> Void)]()
|
||||
|
||||
|
||||
for component in components {
|
||||
switch component {
|
||||
case .single(let view, let maker):
|
||||
case let .single(view, maker):
|
||||
items.append((view, maker))
|
||||
case .result(let existing):
|
||||
case let .result(existing):
|
||||
items.append(contentsOf: existing)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return .result(items)
|
||||
}
|
||||
|
||||
public static func buildBlock(_ components: Component...) -> Component {
|
||||
var items = [(UIView, (ConstraintMaker) -> Void)]()
|
||||
|
||||
|
||||
for component in components {
|
||||
switch component {
|
||||
case .single(let view, let maker):
|
||||
case let .single(view, maker):
|
||||
items.append((view, maker))
|
||||
case .result(let existing):
|
||||
case let .result(existing):
|
||||
items.append(contentsOf: existing)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return .result(items)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,6 @@ import makataInteraction
|
||||
import SnapKit
|
||||
import UIKit
|
||||
|
||||
public var DefaultStackSpacing = CGFloat(8)
|
||||
|
||||
extension UIView: Assignable, Attributable, Withable {}
|
||||
|
||||
public extension UIView {
|
||||
@@ -28,15 +26,6 @@ public extension UIView {
|
||||
return view
|
||||
}
|
||||
|
||||
convenience init(
|
||||
containing: some UIView,
|
||||
constraints: (ConstraintMaker) -> Void = { $0.edges.equalToSuperview() }
|
||||
) {
|
||||
self.init(frame: .zero)
|
||||
|
||||
addSubview(view: containing, constraints: constraints)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func autoFocus() -> Self {
|
||||
becomeFirstResponder()
|
||||
@@ -87,16 +76,6 @@ public extension UIView {
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func addSubview(view: some UIView, constraints: (ConstraintMaker) -> Void) -> Self {
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(view)
|
||||
|
||||
view.snp.makeConstraints(constraints)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func renderSubviews<Value>(
|
||||
from observer: Observable<Value>.Projection,
|
||||
@@ -113,12 +92,12 @@ public extension UIView {
|
||||
|
||||
switch update(value) {
|
||||
case let .single(view, maker):
|
||||
addSubview(view: view, constraints: maker)
|
||||
addSubview(view.defineConstraints(maker))
|
||||
lastReferences = [view]
|
||||
|
||||
case let .result(views):
|
||||
for (view, maker) in views {
|
||||
addSubview(view: view, constraints: maker)
|
||||
addSubview(view.defineConstraints(maker))
|
||||
}
|
||||
|
||||
lastReferences = views.map { $0.0 }
|
||||
@@ -130,165 +109,25 @@ public extension UIView {
|
||||
}
|
||||
}
|
||||
|
||||
public extension UIStackView {
|
||||
static func horizontal(
|
||||
spacing: CGFloat = DefaultStackSpacing,
|
||||
alignment: UIStackView.Alignment = .leading,
|
||||
distribution: UIStackView.Distribution = .fill,
|
||||
@ComponentBuilder components: () -> ComponentBuilder.Component
|
||||
) -> Self {
|
||||
let stack = Self(frame: .zero)
|
||||
stack.axis = .horizontal
|
||||
stack.spacing = spacing
|
||||
stack.alignment = alignment
|
||||
stack.distribution = distribution
|
||||
stack.insetsLayoutMarginsFromSafeArea = false
|
||||
public extension UIView {
|
||||
@available(*, deprecated, message: "Call addSubview with defineConstraints as the last method call in its builder chain instead.")
|
||||
convenience init(
|
||||
containing: some UIView,
|
||||
constraints: (ConstraintMaker) -> Void = { $0.edges.equalToSuperview() }
|
||||
) {
|
||||
self.init(frame: .zero)
|
||||
|
||||
stack.setContentHuggingPriority(.required, for: .vertical)
|
||||
|
||||
switch components() {
|
||||
case let .single(view, maker):
|
||||
stack.addArrangedSubview(view)
|
||||
view.snp.makeConstraints(maker)
|
||||
|
||||
case let .result(views):
|
||||
for (view, maker) in views {
|
||||
stack.addArrangedSubview(view)
|
||||
view.snp.makeConstraints(maker)
|
||||
}
|
||||
}
|
||||
|
||||
return stack
|
||||
}
|
||||
|
||||
static func vertical(
|
||||
spacing: CGFloat = DefaultStackSpacing,
|
||||
alignment: UIStackView.Alignment = .leading,
|
||||
distribution: UIStackView.Distribution = .fill,
|
||||
@ComponentBuilder components: () -> ComponentBuilder.Component
|
||||
) -> Self {
|
||||
let stack = Self(frame: .zero)
|
||||
stack.axis = .vertical
|
||||
stack.spacing = spacing
|
||||
stack.alignment = alignment
|
||||
stack.distribution = distribution
|
||||
stack.insetsLayoutMarginsFromSafeArea = false
|
||||
|
||||
stack.setContentHuggingPriority(.required, for: .vertical)
|
||||
|
||||
switch components() {
|
||||
case let .single(view, maker):
|
||||
stack.addArrangedSubview(view)
|
||||
view.snp.makeConstraints(maker)
|
||||
|
||||
case let .result(views):
|
||||
for (view, maker) in views {
|
||||
stack.addArrangedSubview(view)
|
||||
view.snp.makeConstraints(maker)
|
||||
}
|
||||
}
|
||||
|
||||
return stack
|
||||
addSubview(view: containing, constraints: constraints)
|
||||
}
|
||||
|
||||
@available(*, deprecated, message: "Call addSubview with defineConstraints as the last method call in its builder chain instead.")
|
||||
@discardableResult
|
||||
func margins(_ insets: UIEdgeInsets, relativeToSafeArea: Bool = false) -> Self {
|
||||
isLayoutMarginsRelativeArrangement = true
|
||||
insetsLayoutMarginsFromSafeArea = relativeToSafeArea
|
||||
layoutMargins = insets
|
||||
func addSubview(view: some UIView, constraints: (ConstraintMaker) -> Void) -> Self {
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(view)
|
||||
|
||||
view.snp.makeConstraints(constraints)
|
||||
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
public extension UIStackView {
|
||||
static func renderHorizontal<Value>(
|
||||
from observable: Observable<Value>.Projection,
|
||||
_ lifetime: inout Lifetimeable?,
|
||||
spacing: CGFloat = DefaultStackSpacing,
|
||||
alignment: UIStackView.Alignment = .leading,
|
||||
distribution: UIStackView.Distribution = .fill,
|
||||
@ComponentBuilder components: @escaping @MainActor (Value) -> ComponentBuilder.Component
|
||||
) -> Self {
|
||||
let stack = Self(frame: .zero)
|
||||
stack.axis = .horizontal
|
||||
stack.spacing = spacing
|
||||
stack.alignment = alignment
|
||||
stack.distribution = distribution
|
||||
stack.insetsLayoutMarginsFromSafeArea = false
|
||||
|
||||
stack.setContentHuggingPriority(.required, for: .vertical)
|
||||
|
||||
var lastReferences = [UIView]()
|
||||
|
||||
lifetime = observable.subscribe { [unowned stack] value in
|
||||
DispatchQueue.main.async {
|
||||
lastReferences.forEach {
|
||||
$0.removeFromSuperview()
|
||||
}
|
||||
|
||||
switch components(value) {
|
||||
case let .single(view, maker):
|
||||
stack.addArrangedSubview(view)
|
||||
view.snp.makeConstraints(maker)
|
||||
|
||||
lastReferences = [ view ]
|
||||
case let .result(views):
|
||||
for (view, maker) in views {
|
||||
stack.addArrangedSubview(view)
|
||||
view.snp.makeConstraints(maker)
|
||||
}
|
||||
|
||||
lastReferences = views.map { $0.0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return stack
|
||||
}
|
||||
|
||||
static func renderVertical<Value>(
|
||||
from observable: Observable<Value>.Projection,
|
||||
_ lifetime: inout Lifetimeable?,
|
||||
spacing: CGFloat = DefaultStackSpacing,
|
||||
alignment: UIStackView.Alignment = .leading,
|
||||
distribution: UIStackView.Distribution = .fill,
|
||||
@ComponentBuilder components: @escaping @MainActor (Value) -> ComponentBuilder.Component
|
||||
) -> Self {
|
||||
let stack = Self(frame: .zero)
|
||||
stack.axis = .vertical
|
||||
stack.spacing = spacing
|
||||
stack.alignment = alignment
|
||||
stack.distribution = distribution
|
||||
stack.insetsLayoutMarginsFromSafeArea = false
|
||||
|
||||
stack.setContentHuggingPriority(.required, for: .vertical)
|
||||
|
||||
var lastReferences = [UIView]()
|
||||
|
||||
lifetime = observable.subscribe { [unowned stack] value in
|
||||
DispatchQueue.main.async {
|
||||
lastReferences.forEach {
|
||||
$0.removeFromSuperview()
|
||||
}
|
||||
|
||||
switch components(value) {
|
||||
case let .single(view, maker):
|
||||
stack.addArrangedSubview(view)
|
||||
view.snp.makeConstraints(maker)
|
||||
|
||||
lastReferences = [ view ]
|
||||
case let .result(views):
|
||||
for (view, maker) in views {
|
||||
stack.addArrangedSubview(view)
|
||||
view.snp.makeConstraints(maker)
|
||||
}
|
||||
|
||||
lastReferences = views.map { $0.0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return stack
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user