Experimenting with bindings

This commit is contained in:
Selim Mustafaev 2023-08-02 19:02:06 +03:00
parent b64927ebc2
commit 1d57a17edc
4 changed files with 43 additions and 50 deletions

View File

@ -10,59 +10,55 @@ import Combine
open class ContainerView: UIView {
private var body: UIView = UIView()
public var cancellables: [AnyCancellable] = []
}
public override init(frame: CGRect) {
super.init(frame: frame)
class BindingModel {
translatesAutoresizingMaskIntoConstraints = false
var srcTag: Int
var dstTag: Int
var bind: (ContainerView, Int, Int) -> Void
body = getBody()
body.translatesAutoresizingMaskIntoConstraints = false
addSubview(body)
body.pin(to: self)
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@RootViewBuilder open func getBody() -> UIView {
UIView()
init(srcTag: Int, dstTag: Int, bind: @escaping (ContainerView, Int, Int) -> Void) {
self.srcTag = srcTag
self.dstTag = dstTag
self.bind = bind
}
}
extension UIView {
private func findContainer() -> ContainerView? {
var currentView: UIView = self
while currentView.superview != nil {
if currentView is ContainerView {
return currentView as? ContainerView
}
currentView = currentView.superview!
private struct AssociatedKeys {
static var bindings: Int = 0
}
return nil
var bindings: [BindingModel] {
get {
objc_getAssociatedObject(self, &AssociatedKeys.bindings) as? [BindingModel] ?? []
}
set {
objc_setAssociatedObject(self,
&AssociatedKeys.bindings,
newValue,
objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
public func bind<Src, SrcView, Dst, DstView>(_ prop: ReferenceWritableKeyPath<SrcView,Src>,
to dstTag: Int,
dstProp: KeyPath<DstView,Published<Dst>.Publisher>) -> Self where SrcView: UIView, Src: InitConvertible, Src.Param == Dst {
public func bind<Src, SrcView, Dst, DstView>(_ dstProp: ReferenceWritableKeyPath<DstView,Dst>,
to srcTag: Int,
srcProp: KeyPath<SrcView,Published<Src>.Publisher>) -> Self where DstView: UIView, Dst: InitConvertible, Dst.Param == Src {
guard let container = findContainer(), let srcView = self as? SrcView else {
return self
bindings.append(.init(srcTag: srcTag, dstTag: tag, bind: { container, srcTag, dstTag in
guard let srcView = container.viewWithTag(srcTag) as? SrcView,
let dstView = container.viewWithTag(dstTag) as? DstView
else {
return
}
guard let view = container.viewWithTag(dstTag) as? DstView else {
return self
}
let pub = view[keyPath: dstProp].map { Src($0) }
pub.assign(to: prop, on: srcView)
let publisher = srcView[keyPath: srcProp].map { Dst($0) }
publisher.assign(to: dstProp, on: dstView)
.store(in: &container.cancellables)
}))
return self
}

View File

@ -8,8 +8,7 @@
import UIKit
import Combine
@available(iOS 13.0, *)
public class Stack: UIView {
public class Stack: ContainerView {
public var arrangedSubviews: [UIView]
public var distribution: Distribution

View File

@ -3,8 +3,8 @@ import UIKit
@resultBuilder
public struct RootViewBuilder {
public static func buildBlock(_ components: UIView...) -> UIView {
let view = components.first ?? UIView()
public static func buildBlock(_ components: ContainerView...) -> ContainerView {
let view = components.first ?? ContainerView()
return view.withoutAutoresizing()
}
}

View File

@ -22,19 +22,17 @@ extension Stack.Alignment: InitConvertible {
class ViewController: UIViewController {
let child = TestView()
var child = UIView()
override func viewDidLoad() {
super.viewDidLoad()
child = buildView()
view.addSubview(child)
child.pin(toSafeArea: view, insets: .init(all: 16))
}
}
class TestView: ContainerView {
override func getBody() -> UIView {
@RootViewBuilder func buildView() -> ContainerView {
VStack(alignment: .start, spacing: 16) {
UILabel()
.text("qwe")
@ -49,7 +47,7 @@ class TestView: ContainerView {
YASegmentedControl(items: ["Left", "Center", "Right"])
.tag(100)
}
.bind(\VStack.alignment, to: 100, dstProp: \YASegmentedControl.$selectedIndex)
.bind(\VStack.alignment, to: 100, srcProp: \YASegmentedControl.$selectedIndex)
}
}