Skip to content

Instantly share code, notes, and snippets.

@jshier
Last active April 10, 2017 03:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jshier/3e5451d57911265c4dee679f1d63145a to your computer and use it in GitHub Desktop.
Save jshier/3e5451d57911265c4dee679f1d63145a to your computer and use it in GitHub Desktop.
A stateful view controller
//
// ViewController.swift
// ArchitectureTest
//
// Created by Jon Shier on 6/11/16.
// Copyright © 2016 Jon Shier. All rights reserved.
//
import Alamofire
import Argo
import UIKit
class ViewController: UIViewController, ViewStateful {
var viewStates = ViewStates(loadingView: .blueColor(), errorView: .greenColor(), emptyView: .purpleColor())
var state = ViewControllerState<HTTPBinResponse>.Loading {
didSet {
print(state)
}
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
fetchValue()
}
@IBAction func refreshValue(sender: UIButton) {
refreshValue()
}
}
enum ViewControllerState<T> {
case Loading
case Loaded(state: ViewControllerLoadedState<T>)
case Error(error: APIError)
var value: T? {
switch self {
case let Loaded(state):
return state.value
default:
return nil
}
}
var error: APIError? {
switch self {
case let Loaded(state):
return state.error
case let Error(error):
return error
default:
return nil
}
}
}
enum ViewControllerLoadedState<T> {
case Content(value: T)
case NoContent(value: T)
case Refreshing
case Error(error: APIError)
var value: T? {
switch self {
case let Content(value):
return value
case let NoContent(value): // Will be able to remove duplicate return in Swift 3!
return value
default:
return nil
}
}
var error: APIError? {
switch self {
case let Error(error):
return error
default:
return nil
}
}
}
protocol Stateful: AnyObject {
associatedtype ValueType
var state: ViewControllerState<ValueType> { get set }
}
protocol NetworkLoadable {
static var route: APIRouter { get }
}
extension ViewStateful where Self: UIViewController, ValueType: NetworkLoadable, ValueType: Decodable, ValueType == ValueType.DecodedType, ValueType: ContentAware {
func fetchValue() {
switch state {
case .Loading:
view.backgroundColor = viewStates.loadingView
case .Loaded:
view.backgroundColor = .whiteColor() // reset to default to simulate normal view
case .Error:
view.backgroundColor = viewStates.errorView
}
APIManager.fetch { (response: Response<ValueType, APIError>) in
switch response.result {
case let .Success(value):
self.state = .Loaded(state: (value.hasContent) ? .Content(value: value) : .NoContent(value: value))
case let .Failure(error):
self.state = .Error(error: error)
}
switch self.state {
case .Loading:
self.view.backgroundColor = self.viewStates.loadingView
case let .Loaded(state):
switch state {
case .NoContent:
self.view.backgroundColor = self.viewStates.emptyView
default:
self.view.backgroundColor = .whiteColor()
}
case .Error:
self.view.backgroundColor = self.viewStates.errorView
}
}
}
func refreshValue() {
state = .Loaded(state: .Refreshing)
APIManager.fetch { (response: Response<ValueType, APIError>) in
switch response.result {
case let .Success(value):
self.state = .Loaded(state: (value.hasContent) ? .Content(value: value) : .NoContent(value: value))
case let .Failure(error):
self.state = .Loaded(state: .Error(error: error))
}
switch self.state {
case let .Loaded(state):
switch state {
case .NoContent:
self.view.backgroundColor = self.viewStates.emptyView
default:
self.view.backgroundColor = .whiteColor()
}
default:
self.view.backgroundColor = .whiteColor()
}
}
}
}
extension HTTPBinResponse: NetworkLoadable {
static var route: APIRouter {
return .Default
}
}
protocol ViewStateful: Stateful {
var viewStates: ViewStates { get }
}
struct ViewStates {
let loadingView: UIColor
let errorView: UIColor
let emptyView: UIColor
}
protocol ContentAware {
var hasContent: Bool { get }
}
extension HTTPBinResponse: ContentAware {
var hasContent: Bool {
return false
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment