From 1a4abe5a567edabad7619fedf6acf16bbf130565 Mon Sep 17 00:00:00 2001 From: Selim Mustafaev Date: Thu, 3 Aug 2023 17:16:30 +0300 Subject: [PATCH] Fixing binding --- .../Extensions/UIView/UIView+Binding.swift | 67 +++++++++++++++++ Sources/YadUI/Models/BindingModel.swift | 21 ++++++ Sources/YadUI/Views/ContainerView.swift | 71 +++++++------------ Sources/YadUI/YadUI.swift | 1 + YadUIDemo/YadUIDemo/ViewController.swift | 27 +++---- 5 files changed, 129 insertions(+), 58 deletions(-) create mode 100644 Sources/YadUI/Extensions/UIView/UIView+Binding.swift create mode 100644 Sources/YadUI/Models/BindingModel.swift diff --git a/Sources/YadUI/Extensions/UIView/UIView+Binding.swift b/Sources/YadUI/Extensions/UIView/UIView+Binding.swift new file mode 100644 index 0000000..fbd9fed --- /dev/null +++ b/Sources/YadUI/Extensions/UIView/UIView+Binding.swift @@ -0,0 +1,67 @@ +// +// UIView+Binding.swift +// +// +// Created by Мустафаев Селим Мустафаевич on 03.08.2023. +// + +import UIKit +import Combine + +extension UIView { + + private struct AssociatedKeys { + static var bindings: Int = 0 + } + + var bindings: NSMutableArray { + get { + guard let array = objc_getAssociatedObject(self, &AssociatedKeys.bindings) as? NSMutableArray else { + let newArray = NSMutableArray() + objc_setAssociatedObject(self, + &AssociatedKeys.bindings, + newArray, + objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) + print("New array: ", Unmanaged.passUnretained(newArray).toOpaque()) + return newArray + } + + print("Array: \(Unmanaged.passUnretained(array).toOpaque()), count: \(array.count)") + return array + } + set { + objc_setAssociatedObject(self, + &AssociatedKeys.bindings, + newValue, + objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } + + public func bind(_ dstProp: ReferenceWritableKeyPath, + to srcTag: Int, + srcProp: KeyPath.Publisher>) -> Self where Dst: InitConvertible, Dst.Param == Src { + + bindings.add(BindingModel(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 false + } + + let publisher = srcView[keyPath: srcProp].map { Dst($0) } + publisher.assign(to: dstProp, on: dstView) + .store(in: &container.cancellables) + + return true + })) + + return self + } + + func forEachSubview(_ closure: (UIView) -> Void) { + for view in subviews { + closure(view) + view.forEachSubview(closure) + } + } +} diff --git a/Sources/YadUI/Models/BindingModel.swift b/Sources/YadUI/Models/BindingModel.swift new file mode 100644 index 0000000..c4b9168 --- /dev/null +++ b/Sources/YadUI/Models/BindingModel.swift @@ -0,0 +1,21 @@ +// +// BindingModel.swift +// +// +// Created by Мустафаев Селим Мустафаевич on 03.08.2023. +// + +import Foundation + +class BindingModel { + + var srcTag: Int + var dstTag: Int + var bind: (ContainerView, Int, Int) -> Bool + + init(srcTag: Int, dstTag: Int, bind: @escaping (ContainerView, Int, Int) -> Bool) { + self.srcTag = srcTag + self.dstTag = dstTag + self.bind = bind + } +} diff --git a/Sources/YadUI/Views/ContainerView.swift b/Sources/YadUI/Views/ContainerView.swift index 79f9168..37d25df 100644 --- a/Sources/YadUI/Views/ContainerView.swift +++ b/Sources/YadUI/Views/ContainerView.swift @@ -11,55 +11,34 @@ import Combine open class ContainerView: UIView { public var cancellables: [AnyCancellable] = [] -} - -class BindingModel { - var srcTag: Int - var dstTag: Int - var bind: (ContainerView, Int, Int) -> Void - - init(srcTag: Int, dstTag: Int, bind: @escaping (ContainerView, Int, Int) -> Void) { - self.srcTag = srcTag - self.dstTag = dstTag - self.bind = bind - } -} - -extension UIView { - - private struct AssociatedKeys { - static var bindings: Int = 0 - } - - 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(_ dstProp: ReferenceWritableKeyPath, - to srcTag: Int, - srcProp: KeyPath.Publisher>) -> Self where DstView: UIView, Dst: InitConvertible, Dst.Param == Src { + func processBindings() { - 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 + bindings.setArray(processBindings(from: bindings)) + + forEachSubview { view in + if let container = view as? ContainerView { + container.processBindings() } - let publisher = srcView[keyPath: srcProp].map { Dst($0) } - publisher.assign(to: dstProp, on: dstView) - .store(in: &container.cancellables) - })) - - return self + bindings.addObjects(from: processBindings(from: view.bindings)) + } + } + + func processBindings(from array: NSArray) -> [BindingModel] { + + var failedBindings: [BindingModel] = [] + + for binding in array { + guard let binding = binding as? BindingModel else { + continue + } + + if !binding.bind(self, binding.srcTag, binding.dstTag) { + failedBindings.append(binding) + } + } + + return failedBindings } } diff --git a/Sources/YadUI/YadUI.swift b/Sources/YadUI/YadUI.swift index 9e0c351..40f36a9 100644 --- a/Sources/YadUI/YadUI.swift +++ b/Sources/YadUI/YadUI.swift @@ -5,6 +5,7 @@ public struct RootViewBuilder { public static func buildBlock(_ components: ContainerView...) -> ContainerView { let view = components.first ?? ContainerView() + view.processBindings() return view.withoutAutoresizing() } } diff --git a/YadUIDemo/YadUIDemo/ViewController.swift b/YadUIDemo/YadUIDemo/ViewController.swift index 041c132..e256b89 100644 --- a/YadUIDemo/YadUIDemo/ViewController.swift +++ b/YadUIDemo/YadUIDemo/ViewController.swift @@ -33,21 +33,24 @@ class ViewController: UIViewController { } @RootViewBuilder func buildView() -> ContainerView { - VStack(alignment: .start, spacing: 16) { - UILabel() - .text("qwe") - .background(.blue) - UILabel() - .text("asdf") - .background(.blue) - UILabel() - .text("zxcv") - .background(.blue) - .hugging(.defaultLowMinus) + VStack(alignment: .fill, spacing: 16) { + VStack(alignment: .start, spacing: 16) { + UILabel() + .text("qwe") + .background(.blue) + UILabel() + .text("asdf") + .background(.blue) + UILabel() + .text("zxcv") + .background(.blue) + .hugging(.defaultLowMinus) + } + .bind(\VStack.alignment, to: 100, srcProp: \YASegmentedControl.$selectedIndex) + YASegmentedControl(items: ["Left", "Center", "Right"]) .tag(100) } - .bind(\VStack.alignment, to: 100, srcProp: \YASegmentedControl.$selectedIndex) } }