Skip to content

Instantly share code, notes, and snippets.

@chriseidhof
Created February 15, 2018 10:23
Show Gist options
  • Select an option

  • Save chriseidhof/c722698d5aed1acf8f04e954f189e4b3 to your computer and use it in GitHub Desktop.

Select an option

Save chriseidhof/c722698d5aed1acf8f04e954f189e4b3 to your computer and use it in GitHub Desktop.

Revisions

  1. chriseidhof created this gist Feb 15, 2018.
    104 changes: 104 additions & 0 deletions mavb-minimal.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,104 @@
    //: [Previous](@previous)

    import Foundation

    struct Counter {
    var name: String = ""
    var age: Int = 33

    enum Action {
    case increment
    case setName(String?)
    }

    mutating func send(_ action: Action) {
    switch action {
    case .increment: age += 1
    case .setName(let n):
    name = n ?? ""
    }
    }
    }

    final class Adapter<State, Action> {
    let mutate: (inout State, Action) -> ()
    private(set) var state: State {
    didSet {
    observers.forEach { $0(state) }
    }
    }
    var observers: [(State) -> ()] = []

    init(_ initial: State, mutate: @escaping (inout State, Action) -> ()) {
    self.state = initial
    self.mutate = mutate
    }

    func send(_ action: Action) {
    mutate(&self.state, action)
    }

    func addObserver(_ o: @escaping (State) -> ()) {
    o(state)
    observers.append(o)
    }
    }

    enum Bindable<Source,A> {
    case constant(A)
    case dynamic(KeyPath<Source,A>)
    }

    extension Bindable {
    func get(_ s: Source) -> A {
    switch self {
    case .constant(let value): return value
    case .dynamic(let kp): return s[keyPath: kp]
    }
    }
    }

    struct TextFieldBinder<State, Action> {
    var text: Bindable<State, String>?
    var onChange: (String?) -> Action
    }

    final class Box<A> {
    let value: A
    var strongReferences: [Any] = []
    init(_ value: A) {
    self.value = value
    }
    }

    final class TargetAction {
    let callback: () -> ()
    init(_ callback: @escaping () -> ()) {
    self.callback = callback
    }

    @objc func action(_ sender: Any) {
    callback()
    }
    }

    import UIKit
    func run<State, Action>(_ binder: TextFieldBinder<State, Action>, adapter: Adapter<State, Action>) -> Box<UITextField> {
    let result = UITextField()
    let box = Box(result)
    result.text = binder.text?.get(adapter.state)
    let ta = TargetAction { [unowned result, unowned adapter] in
    adapter.send(binder.onChange(result.text))
    }
    box.strongReferences.append(ta)
    adapter.addObserver { [weak result] in
    result?.text = binder.text?.get($0)
    }

    return Box(result)
    }

    let sample = TextFieldBinder<Counter, Counter.Action>(text: .constant("hello"), onChange: Counter.Action.setName)

    let label = run(sample, adapter: Adapter(Counter(), mutate: { $0.send($1) }))
    label.value.text = "hello"