Skip to content

Commit

Permalink
Added Binder
Browse files Browse the repository at this point in the history
  • Loading branch information
hip4yes committed Feb 3, 2020
1 parent e75067c commit 5ce70df
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 3 deletions.
2 changes: 1 addition & 1 deletion Aurum.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
106 changes: 106 additions & 0 deletions Aurum/Classes/AurumBinder.swift
Original file line number Diff line number Diff line change
@@ -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<T>(_ value: T?, resolver: (T?) -> Void){
if value != nil || forceSetNils { resolver(value) }
}
}

public protocol UIComponent {
associatedtype ComponentData
associatedtype ProducedData

var produced: Subject<ProducedData, Never> { get }

func setup()

func update(data: ComponentData)
}

public class BindingBase<S: AurumState, C: UIComponent>{
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<S: AurumState, A: AurumAction> = (AurumStore<S, A>) -> Void

public class Binding<S: AurumState, C: UIComponent, A: AurumAction>{//: 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<S, A>) {
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<S: AurumState, A: AurumAction> {
let bindings: [BindingSetup<S, A>]

public init(_ bindings: BindingSetup<S, A>...) {
self.bindings = bindings
}

private init(){
bindings = []
}

func setup(store: AurumStore<S, A>) {
bindings.forEach { $0(store) }
}

public static func none<S, A>() -> Bindings<S, A>{
return Bindings<S, A>()
}
}


public func ~><S: AurumState, C: UIComponent>(left: @escaping (S) -> C.ComponentData, right: C) -> BindingBase<S, C>{
return BindingBase(extractor: left, component: right)
}

public func ~><S: AurumState, C: UIComponent>(left: KeyPath<S, C.ComponentData>, right: C) -> BindingBase<S, C>{
return BindingBase(extractor: { $0[keyPath: left] }, component: right)
}

public func ~><S: AurumState, C: UIComponent, A: AurumAction>(left: BindingBase<S, C>, right: @escaping (C.ProducedData) -> A?) -> BindingSetup<S, A>{
return Binding(extractor: left.extractor, component: left.component, action: right).setup
}

public func ~><S: AurumState, C: UIComponent, A: AurumAction>(left: BindingBase<S, C>, right: A) -> BindingSetup<S, A>{
return Binding(extractor: left.extractor, component: left.component, action: { _ in right }).setup
}

public func ~><S: AurumState, C: UIComponent, A: AurumAction>(left: BindingBase<S, C>, right: Void) -> BindingSetup<S, A>{
return Binding(extractor: left.extractor, component: left.component, action: { _ in nil }).setup
}
5 changes: 4 additions & 1 deletion Aurum/Classes/AurumController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@ public protocol AurumStoreSetupable {
public protocol AurumController: class, AurumStoreSetupable {
associatedtype State: AurumState
associatedtype Action: AurumAction

var store: AurumStore<State, Action>! { get set }
var bindings: Bindings<State, Action> { get }
}

public extension AurumController{
func set<S, A>(store: AurumStore<S, A>){
guard let s = store as? AurumStore<State, Action> 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)
}
}

Expand All @@ -41,7 +44,7 @@ public extension AurumController{
}


infix operator ~>
infix operator ~>: AdditionPrecedence

public func ~><T: AurumController>(left: (T, UIButton), right: T.Action){
left.1.reactive.tap.replaceElements(with: right).bind(to: left.0.reduce)
Expand Down
2 changes: 1 addition & 1 deletion Aurum/Classes/AurumModuleConfigurator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
59 changes: 59 additions & 0 deletions Aurum/Classes/UIComponents/ButtonComponent.swift
Original file line number Diff line number Diff line change
@@ -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<Void, Never> {
if let produced = objc_getAssociatedObject(self, &UIButton.AssociatedKeys.Subject) {
return produced as! Subject<Void, Never>
} else {
let produced = Subject<Void, Never>()
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)
}
}
13 changes: 13 additions & 0 deletions Example/Pods/Pods.xcodeproj/project.pbxproj

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 5ce70df

Please sign in to comment.