Skip to content

Instantly share code, notes, and snippets.

@timstudt
Created January 17, 2019 09:11
Show Gist options
  • Save timstudt/cf58b5633477acadf0a94516b693f9e8 to your computer and use it in GitHub Desktop.
Save timstudt/cf58b5633477acadf0a94516b693f9e8 to your computer and use it in GitHub Desktop.

Revisions

  1. timstudt created this gist Jan 17, 2019.
    140 changes: 140 additions & 0 deletions ViewStates.playground
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,140 @@
    //: A UIKit based Playground for presenting user interface

    import UIKit
    import PlaygroundSupport

    // Models

    struct MyViewModel: Equatable { let text: String }

    enum MyError: Error, Equatable {
    case noConnection(String)
    }

    // Protocols

    protocol ViewState: Equatable {
    associatedtype T: Equatable
    associatedtype Error: Equatable
    var isLoading: Bool { get }
    var data: T? { get }
    var error: Error? { get }
    }

    protocol Presenting {
    func loadData()
    }

    protocol UI: class {
    associatedtype State
    func render(state: State)
    }

    // Base Classes
    class Presenter<V: UI> {
    weak var ui: V?
    }

    // Module Classes

    final class MyPresenter: Presenter<MyView>, Presenting {
    func loadData() {
    ui?.render(state: .loading)
    DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
    self.ui?.render(state: .error(.noConnection("oops, we messed up!")))
    }
    DispatchQueue.main.asyncAfter(deadline: .now() + 4) {
    self.ui?.render(state: .loaded(.init(text: "kidding..got some data")))
    }
    }
    }

    class MyView : UIViewController {
    let presenter: MyPresenter
    var lable: UILabel!

    init(presenter: MyPresenter = MyPresenter()) {
    self.presenter = presenter
    super.init(nibName: nil, bundle: nil)
    presenter.ui = self
    }

    required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
    }

    override func loadView() {
    let view = UIView()
    view.backgroundColor = .white

    let label = UILabel()
    label.frame = CGRect(x: 150, y: 200, width: 250, height: 20)
    label.text = "Hello World!"
    label.textColor = .black

    view.addSubview(label)
    self.lable = label
    self.view = view
    }

    override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    presenter.loadData()
    }

    private func showSpinner() {
    lable.textColor = .gray
    lable.text = "..loading"
    }

    private func show(data: MyViewModel) {
    lable.textColor = .black
    lable.text = data.text
    }

    private func show(error: MyError) {
    lable.textColor = .red
    switch error {
    case .noConnection(let mesg):
    lable.text = mesg
    }
    }
    }

    extension MyView: UI {
    typealias T = State
    func render(state: State) {
    switch state {
    case .loading: showSpinner()
    case .loaded(let data): show(data: data)
    case .error(let error): show(error: error)
    }
    }
    }

    extension MyView {
    enum State {
    typealias T = MyViewModel
    typealias Error = MyError
    case loading, loaded(T), error(Error)
    }
    }

    extension MyView.State: ViewState {
    var isLoading: Bool { return self == .loading }
    var data: MyViewModel? {
    switch self {
    case .loading, .error: return nil
    case .loaded(let data): return data
    }
    }
    var error: MyError? {
    switch self {
    case .loading, .loaded: return nil
    case .error(let mesg): return mesg
    }
    }
    }

    // Present the view controller in the Live View window
    PlaygroundPage.current.liveView = MyView()