Experimenting with bindings
This commit is contained in:
parent
b64927ebc2
commit
1d57a17edc
@ -10,59 +10,55 @@ import Combine
|
|||||||
|
|
||||||
open class ContainerView: UIView {
|
open class ContainerView: UIView {
|
||||||
|
|
||||||
private var body: UIView = UIView()
|
|
||||||
public var cancellables: [AnyCancellable] = []
|
public var cancellables: [AnyCancellable] = []
|
||||||
|
}
|
||||||
|
|
||||||
public override init(frame: CGRect) {
|
class BindingModel {
|
||||||
super.init(frame: frame)
|
|
||||||
|
|
||||||
translatesAutoresizingMaskIntoConstraints = false
|
var srcTag: Int
|
||||||
|
var dstTag: Int
|
||||||
|
var bind: (ContainerView, Int, Int) -> Void
|
||||||
|
|
||||||
body = getBody()
|
init(srcTag: Int, dstTag: Int, bind: @escaping (ContainerView, Int, Int) -> Void) {
|
||||||
body.translatesAutoresizingMaskIntoConstraints = false
|
self.srcTag = srcTag
|
||||||
addSubview(body)
|
self.dstTag = dstTag
|
||||||
body.pin(to: self)
|
self.bind = bind
|
||||||
}
|
|
||||||
|
|
||||||
required public init?(coder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
@RootViewBuilder open func getBody() -> UIView {
|
|
||||||
UIView()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension UIView {
|
extension UIView {
|
||||||
|
|
||||||
private func findContainer() -> ContainerView? {
|
private struct AssociatedKeys {
|
||||||
|
static var bindings: Int = 0
|
||||||
var currentView: UIView = self
|
|
||||||
while currentView.superview != nil {
|
|
||||||
if currentView is ContainerView {
|
|
||||||
return currentView as? ContainerView
|
|
||||||
}
|
|
||||||
currentView = currentView.superview!
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func bind<Src, SrcView, Dst, DstView>(_ prop: ReferenceWritableKeyPath<SrcView,Src>,
|
var bindings: [BindingModel] {
|
||||||
to dstTag: Int,
|
get {
|
||||||
dstProp: KeyPath<DstView,Published<Dst>.Publisher>) -> Self where SrcView: UIView, Src: InitConvertible, Src.Param == Dst {
|
objc_getAssociatedObject(self, &AssociatedKeys.bindings) as? [BindingModel] ?? []
|
||||||
|
|
||||||
guard let container = findContainer(), let srcView = self as? SrcView else {
|
|
||||||
return self
|
|
||||||
}
|
}
|
||||||
|
set {
|
||||||
guard let view = container.viewWithTag(dstTag) as? DstView else {
|
objc_setAssociatedObject(self,
|
||||||
return self
|
&AssociatedKeys.bindings,
|
||||||
|
newValue,
|
||||||
|
objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let pub = view[keyPath: dstProp].map { Src($0) }
|
public func bind<Src, SrcView, Dst, DstView>(_ dstProp: ReferenceWritableKeyPath<DstView,Dst>,
|
||||||
pub.assign(to: prop, on: srcView)
|
to srcTag: Int,
|
||||||
.store(in: &container.cancellables)
|
srcProp: KeyPath<SrcView,Published<Src>.Publisher>) -> Self where DstView: UIView, Dst: InitConvertible, Dst.Param == Src {
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
let publisher = srcView[keyPath: srcProp].map { Dst($0) }
|
||||||
|
publisher.assign(to: dstProp, on: dstView)
|
||||||
|
.store(in: &container.cancellables)
|
||||||
|
}))
|
||||||
|
|
||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,8 +8,7 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
|
|
||||||
@available(iOS 13.0, *)
|
public class Stack: ContainerView {
|
||||||
public class Stack: UIView {
|
|
||||||
|
|
||||||
public var arrangedSubviews: [UIView]
|
public var arrangedSubviews: [UIView]
|
||||||
public var distribution: Distribution
|
public var distribution: Distribution
|
||||||
|
|||||||
@ -3,8 +3,8 @@ import UIKit
|
|||||||
@resultBuilder
|
@resultBuilder
|
||||||
public struct RootViewBuilder {
|
public struct RootViewBuilder {
|
||||||
|
|
||||||
public static func buildBlock(_ components: UIView...) -> UIView {
|
public static func buildBlock(_ components: ContainerView...) -> ContainerView {
|
||||||
let view = components.first ?? UIView()
|
let view = components.first ?? ContainerView()
|
||||||
return view.withoutAutoresizing()
|
return view.withoutAutoresizing()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,19 +22,17 @@ extension Stack.Alignment: InitConvertible {
|
|||||||
|
|
||||||
class ViewController: UIViewController {
|
class ViewController: UIViewController {
|
||||||
|
|
||||||
let child = TestView()
|
var child = UIView()
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
child = buildView()
|
||||||
view.addSubview(child)
|
view.addSubview(child)
|
||||||
child.pin(toSafeArea: view, insets: .init(all: 16))
|
child.pin(toSafeArea: view, insets: .init(all: 16))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
class TestView: ContainerView {
|
@RootViewBuilder func buildView() -> ContainerView {
|
||||||
|
|
||||||
override func getBody() -> UIView {
|
|
||||||
VStack(alignment: .start, spacing: 16) {
|
VStack(alignment: .start, spacing: 16) {
|
||||||
UILabel()
|
UILabel()
|
||||||
.text("qwe")
|
.text("qwe")
|
||||||
@ -49,7 +47,7 @@ class TestView: ContainerView {
|
|||||||
YASegmentedControl(items: ["Left", "Center", "Right"])
|
YASegmentedControl(items: ["Left", "Center", "Right"])
|
||||||
.tag(100)
|
.tag(100)
|
||||||
}
|
}
|
||||||
.bind(\VStack.alignment, to: 100, dstProp: \YASegmentedControl.$selectedIndex)
|
.bind(\VStack.alignment, to: 100, srcProp: \YASegmentedControl.$selectedIndex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user