Fixing binding

This commit is contained in:
Selim Mustafaev 2023-08-03 17:16:30 +03:00
parent 1d57a17edc
commit 1a4abe5a56
5 changed files with 129 additions and 58 deletions

View File

@ -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<Src, SrcView, Dst, DstView>(_ dstProp: ReferenceWritableKeyPath<DstView,Dst>,
to srcTag: Int,
srcProp: KeyPath<SrcView,Published<Src>.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)
}
}
}

View File

@ -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
}
}

View File

@ -11,55 +11,34 @@ import Combine
open class ContainerView: UIView {
public var cancellables: [AnyCancellable] = []
}
class BindingModel {
func processBindings() {
var srcTag: Int
var dstTag: Int
var bind: (ContainerView, Int, Int) -> Void
bindings.setArray(processBindings(from: bindings))
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<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 {
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
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)
}))
bindings.addObjects(from: processBindings(from: view.bindings))
}
}
return self
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
}
}

View File

@ -5,6 +5,7 @@ public struct RootViewBuilder {
public static func buildBlock(_ components: ContainerView...) -> ContainerView {
let view = components.first ?? ContainerView()
view.processBindings()
return view.withoutAutoresizing()
}
}

View File

@ -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)
}
}