Skip to content

Instantly share code, notes, and snippets.

@nathanborror
Last active August 25, 2015 22:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nathanborror/2a4335b7f82781136e6b to your computer and use it in GitHub Desktop.
Save nathanborror/2a4335b7f82781136e6b to your computer and use it in GitHub Desktop.
struct Item {
let title: String
}
// Resources
let itemResource = Resource<Item> { callback in
let items = [Item(title: "Foo"), Item(title: "Bar"), Item(title: "Baz")]
callback(items)
}
let subItemResource: Item -> Resource<Item> = { item in
return Resource<Item> { callback in
let items = [Item(title: "Foo"), Item(title: "Bar"), Item(title: "Baz")]
callback(items)
}
}
// ViewControllers
let itemController: Screen<Item> = tableViewController(itemResource, configuration: titleCell { $0.title })
let subItemController: Item -> Screen<Item> = { item in
return tableViewController(subItemResource(item), configuration: titleCell { $0.title })
}
// Flow
let flow = navigationController(itemController) >>> subItemController
flow.run()
import UIKit
class Box<T> {
let unbox: T
init(_ value: T) { self.unbox = value }
}
extension UIViewController {
func applyNavigationItem(navigationItem: NavigationItem) {
self.navigationItem.title = navigationItem.title
}
}
/// Screen is capable of build a UIViewController of a given type and
/// associating it with an optional UINavigationItem.
struct Screen<A> {
typealias BuildHandler = (A -> Void) -> UIViewController
let build: BuildHandler
var navigationItem: NavigationItem
init(_ navigationItem: NavigationItem? = nil, _ build: BuildHandler) {
self.build = build
self.navigationItem = navigationItem ?? defaultNavigationItem
}
func run(fn: A -> Void) -> UIViewController {
let vc = self.build(fn)
vc.applyNavigationItem(navigationItem)
return vc
}
}
/// Resource is a thin wrapper around async data loading.
struct Resource<A> {
typealias LoadHandler = ([A] -> Void) -> Void
let load: LoadHandler
init(load: LoadHandler) {
self.load = load
}
}
/// Navigation is capable of building a UINavigationController to manage Screens.
struct Navigation<A> {
let build: (fn: (A, UINavigationController) -> Void) -> UINavigationController
func run() -> UINavigationController {
return build { _ in }
}
}
struct NavigationItem {
var title: String?
}
var defaultNavigationItem = NavigationItem(title: nil)
func navigationController<A>(screen: Screen<A>) -> Navigation<A> {
return Navigation { callback in
let nav = UINavigationController()
nav.viewControllers = [screen.run { callback($0, nav) }]
return nav
}
}
infix operator >>> { associativity left }
func >>><A,B>(l: Navigation<A>, r: A -> Screen<B>) -> Navigation<B> {
return Navigation { callback -> UINavigationController in
let navigation = l.build { a, nc in
let rvc = r(a).run { callback($0, nc) }
nc.pushViewController(rvc, animated: true)
}
return navigation
}
}
// Cells
struct CellConfiguration<A> {
var render: (UITableViewCell, A) -> Void = { _ in }
var style: UITableViewCellStyle = .Default
}
func titleCell<A>(fn: A -> String) -> CellConfiguration<A> {
var config = CellConfiguration<A>()
config.render = { cell, a in
cell.textLabel?.text = fn(a)
}
return config
}
// TableViewController
func tableViewController<A>(resource: Resource<A>, configuration: CellConfiguration<A>, navigationItem: NavigationItem = defaultNavigationItem) -> Screen<A> {
return Screen(navigationItem) { callback in
let tvc = TableViewController(style: .Plain)
tvc.callback = { x in
guard let boxed = x as? Box<A> else { return }
callback(boxed.unbox)
}
tvc.cellStyle = configuration.style
tvc.configureCell = { cell, data in
guard let boxed = data as? Box<A> else { return cell }
configuration.render(cell, boxed.unbox)
return cell
}
resource.load { items in
tvc.items = items.map { Box($0) }
}
return tvc
}
}
class TableViewController: UITableViewController {
var items = [AnyObject]() {
didSet {
self.view.backgroundColor = UIColor.whiteColor()
self.tableView.reloadData()
}
}
var callback: AnyObject -> Void = {_ in}
var configureCell: (UITableViewCell, AnyObject) -> UITableViewCell = { cell, _ in cell }
var cellStyle: UITableViewCellStyle = .Default
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: cellStyle, reuseIdentifier: nil)
let data = items[indexPath.row]
return configureCell(cell, data)
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let data = items[indexPath.row]
callback(data)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment