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.
Swift Playground: View State for communicating changes from Presenter to View
//: 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()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment