diff --git a/Aurum.podspec b/Aurum.podspec index 5bc015e..1a9672a 100644 --- a/Aurum.podspec +++ b/Aurum.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'Aurum' - s.version = '1.0.1' + s.version = '1.1.0' s.summary = 'Evolution of Amber architecture' # This description is used to generate tags and improve search results. diff --git a/Aurum/Classes/AurumBinder.swift b/Aurum/Classes/AurumBinder.swift new file mode 100644 index 0000000..76b086a --- /dev/null +++ b/Aurum/Classes/AurumBinder.swift @@ -0,0 +1,106 @@ +// +// AurumBinder.swift +// Bond +// +// Created by Nikita Arkhipov on 03.02.2020. +// + +import Foundation +import ReactiveKit +import Bond + +public class BaseComponentData{ + let forceSetNils: Bool + + init(forceSetNils: Bool) { + self.forceSetNils = forceSetNils + } + + func resolve(_ value: T?, resolver: (T?) -> Void){ + if value != nil || forceSetNils { resolver(value) } + } +} + +public protocol UIComponent { + associatedtype ComponentData + associatedtype ProducedData + + var produced: Subject { get } + + func setup() + + func update(data: ComponentData) +} + +public class BindingBase{ + let extractor: (S) -> C.ComponentData + let component: C + + init(extractor: @escaping (S) -> C.ComponentData, component: C) { + self.extractor = extractor + self.component = component + } +} + +public typealias BindingSetup = (AurumStore) -> Void + +public class Binding{//: BindingSetupable { + let extractor: (S) -> C.ComponentData + let component: C + let action: (C.ProducedData) -> A? + + init(extractor: @escaping (S) -> C.ComponentData, component: C, action: @escaping (C.ProducedData) -> A?) { + self.extractor = extractor + self.component = component + self.action = action + } + + func setup(store: AurumStore) { + component.setup() + _ = store.state.map(extractor).observeNext { [weak self] s in + self?.component.update(data: s) + } + component.produced.map(action).ignoreNils().bind(to: store.reducer) + } +} + +public class Bindings { + let bindings: [BindingSetup] + + public init(_ bindings: BindingSetup...) { + self.bindings = bindings + } + + private init(){ + bindings = [] + } + + func setup(store: AurumStore) { + bindings.forEach { $0(store) } + } + + public static func none() -> Bindings{ + return Bindings() + } +} + + +public func ~>(left: @escaping (S) -> C.ComponentData, right: C) -> BindingBase{ + return BindingBase(extractor: left, component: right) +} + +public func ~>(left: KeyPath, right: C) -> BindingBase{ + return BindingBase(extractor: { $0[keyPath: left] }, component: right) +} + +public func ~>(left: BindingBase, right: @escaping (C.ProducedData) -> A?) -> BindingSetup{ + return Binding(extractor: left.extractor, component: left.component, action: right).setup +} + +public func ~>(left: BindingBase, right: A) -> BindingSetup{ + return Binding(extractor: left.extractor, component: left.component, action: { _ in right }).setup +} + +public func ~>(left: BindingBase, right: Void) -> BindingSetup{ + return Binding(extractor: left.extractor, component: left.component, action: { _ in nil }).setup +} diff --git a/Aurum/Classes/AurumController.swift b/Aurum/Classes/AurumController.swift index e4bc774..36746ea 100644 --- a/Aurum/Classes/AurumController.swift +++ b/Aurum/Classes/AurumController.swift @@ -17,13 +17,16 @@ public protocol AurumStoreSetupable { public protocol AurumController: class, AurumStoreSetupable { associatedtype State: AurumState associatedtype Action: AurumAction + var store: AurumStore! { get set } + var bindings: Bindings { get } } public extension AurumController{ func set(store: AurumStore){ guard let s = store as? AurumStore else { fatalError("\(type(of: self)) failed to set store: expected <\(State.self), \(Action.self)> got <\(S.self), \(A.self)>") } self.store = s + self.bindings.setup(store: s) } } @@ -41,7 +44,7 @@ public extension AurumController{ } -infix operator ~> +infix operator ~>: AdditionPrecedence public func ~>(left: (T, UIButton), right: T.Action){ left.1.reactive.tap.replaceElements(with: right).bind(to: left.0.reduce) diff --git a/Aurum/Classes/AurumModuleConfigurator.swift b/Aurum/Classes/AurumModuleConfigurator.swift index e9c02bf..b6447e4 100644 --- a/Aurum/Classes/AurumModuleConfigurator.swift +++ b/Aurum/Classes/AurumModuleConfigurator.swift @@ -36,7 +36,7 @@ public extension AurumModuleConfigurator{ guard let vcs = vc as? AurumStoreSetupable else { fatalError("\(type(of: vc)) does not conforms to AurumController") } vcs.set(store: store.wrapped()) - + didLoad(state: state, actor: store.actor) return AurumModuleData(controller: vc, inputActionListener: store.inputReduce) diff --git a/Aurum/Classes/UIComponents/ButtonComponent.swift b/Aurum/Classes/UIComponents/ButtonComponent.swift new file mode 100644 index 0000000..6275621 --- /dev/null +++ b/Aurum/Classes/UIComponents/ButtonComponent.swift @@ -0,0 +1,59 @@ +// +// ButtonComponent.swift +// Bond +// +// Created by Nikita Arkhipov on 03.02.2020. +// + +import UIKit +import ReactiveKit +import Bond + +public class ButtonData: BaseComponentData{ + let title: String? + let titleColor: UIColor? + let image: UIImage? + let backgroundImage: UIImage? + let backgroundColor: UIColor? + + public init(title: String? = nil, titleColor: UIColor? = nil, image: UIImage? = nil, backgroundImage: UIImage? = nil, backgroundColor: UIColor? = nil, forceSetNils: Bool = false) { + self.title = title + self.titleColor = titleColor + self.image = image + self.backgroundImage = backgroundImage + self.backgroundColor = backgroundColor + super.init(forceSetNils: forceSetNils) + } + + func update(_ b: UIButton){ + resolve(title) { b.setTitle($0, for: .normal) } + resolve(titleColor) { b.setTitleColor($0, for: .normal) } + resolve(image) { b.setImage($0, for: .normal) } + resolve(backgroundImage) { b.setBackgroundImage($0, for: .normal) } + resolve(backgroundColor) { b.backgroundColor = $0 } + } +} + +extension UIButton: UIComponent{ + private struct AssociatedKeys { + static var Subject = "Subject" + } + + public var produced: Subject { + if let produced = objc_getAssociatedObject(self, &UIButton.AssociatedKeys.Subject) { + return produced as! Subject + } else { + let produced = Subject() + objc_setAssociatedObject(self, &UIButton.AssociatedKeys.Subject, produced, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) + return produced + } + } + + public func setup() { + reactive.tap.bind(to: produced) + } + + public func update(data: ButtonData){ + data.update(self) + } +} diff --git a/Example/Pods/Pods.xcodeproj/project.pbxproj b/Example/Pods/Pods.xcodeproj/project.pbxproj index 81065d2..a6e906c 100644 --- a/Example/Pods/Pods.xcodeproj/project.pbxproj +++ b/Example/Pods/Pods.xcodeproj/project.pbxproj @@ -243,6 +243,8 @@ 1E16ADA023D8AE3B00CBB142 /* AurumStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AurumStore.swift; path = Aurum/Classes/AurumStore.swift; sourceTree = ""; }; 1E16ADA123D8AE3B00CBB142 /* AurumMiddleware.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AurumMiddleware.swift; path = Aurum/Classes/AurumMiddleware.swift; sourceTree = ""; }; 1E78E5FC92106B08D34468CF40BCF254 /* ReactiveKit.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = ReactiveKit.modulemap; sourceTree = ""; }; + 1E7C3FD623E86A9E009F2F4A /* AurumBinder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AurumBinder.swift; path = Aurum/Classes/AurumBinder.swift; sourceTree = ""; }; + 1E7C3FD823E86C09009F2F4A /* ButtonComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonComponent.swift; sourceTree = ""; }; 20FA217225B82E451C8FC48B5BF67BC9 /* Subscribers.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Subscribers.swift; path = Sources/Subscribers.swift; sourceTree = ""; }; 212B5192B8D9D652CD1FE4303F9072E1 /* SignalProtocol+Optional.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "SignalProtocol+Optional.swift"; path = "Sources/SignalProtocol+Optional.swift"; sourceTree = ""; }; 21ED909E65616DF14E0CAE8499B340D3 /* OrderedCollectionDiff+Strideable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "OrderedCollectionDiff+Strideable.swift"; path = "Sources/Bond/Observable Collections/OrderedCollectionDiff+Strideable.swift"; sourceTree = ""; }; @@ -445,6 +447,15 @@ name = iOS; sourceTree = ""; }; + 1E7C3FD723E86BFB009F2F4A /* UIComponents */ = { + isa = PBXGroup; + children = ( + 1E7C3FD823E86C09009F2F4A /* ButtonComponent.swift */, + ); + name = UIComponents; + path = Aurum/Classes/UIComponents; + sourceTree = ""; + }; 4D83B846F1169B3E24E6A03DA2DD26C2 /* Support Files */ = { isa = PBXGroup; children = ( @@ -503,6 +514,8 @@ 1E16AD9A23D8AE3B00CBB142 /* AurumReducer.swift */, 1E16AD9B23D8AE3B00CBB142 /* AurumSimulation.swift */, 1E16ADA023D8AE3B00CBB142 /* AurumStore.swift */, + 1E7C3FD623E86A9E009F2F4A /* AurumBinder.swift */, + 1E7C3FD723E86BFB009F2F4A /* UIComponents */, E26CE0FADA9842D9D07C7EA558528D6A /* Pod */, C2D4208909963C215F9944A72A04F51E /* Support Files */, );