Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Data-Driven View Controllers. Tips and Tricks

DAloG
November 25, 2017

Data-Driven View Controllers. Tips and Tricks

Huge pile of goodness related to data-driven UI on iOS

DAloG

November 25, 2017
Tweet

More Decks by DAloG

Other Decks in Programming

Transcript

  1. What makes good view controller? → predictable and stable behavior

    → easy to test and reproduce issues → minimum amount of business code → sync interface by @DAlooG // 2017 @ GDG Dev Fest
  2. What makes good abstractions? → fully incapsulated context → operation

    order agnostic → decoupled from rest of the system by @DAlooG // 2017 @ GDG Dev Fest
  3. Usual view controller class ViewController: UIViewController { var database =

    DatabaseWrapper.shared var network = NetworkAPI.shared ... } by @DAlooG // 2017 @ GDG Dev Fest
  4. What is wrong with this? → user action are not

    visible, nobody can react. → change in core components can break everything. → zero reuse between parts/projects. by @DAlooG // 2017 @ GDG Dev Fest
  5. View controller with Props class ViewController: UIViewController { struct Props

    { ... } func render(props: Props) { ... } } by @DAlooG // 2017 @ GDG Dev Fest
  6. What is good with this? → code locality is great

    → change in app is not reflected in VC → all inputs and outputs is visible by @DAlooG // 2017 @ GDG Dev Fest
  7. Can we generalize this? protocol DataDriven { associatedtype Props func

    render(props: Props) } by @DAlooG // 2017 @ GDG Dev Fest
  8. Should we? Probably, no. Exact shape is not so relevant.

    Ideas matter more that implementation details by @DAlooG // 2017 @ GDG Dev Fest
  9. How we can handle view actions? → action closure →

    Command pattern → dispatch closure by @DAlooG // 2017 @ GDG Dev Fest
  10. Action closure Simplest possible idea struct Props { struct Item

    { ... } let data: [Item] let reload: () -> () let select: (Item) -> () } by @DAlooG // 2017 @ GDG Dev Fest
  11. Command pattern struct Command<T> { init(file: StaticString = #file, line:

    Int = #line, action: (T) -> ()) { ... } func perform(with value: T) { action(value) } } by @DAlooG // 2017 @ GDG Dev Fest
  12. Command usage struct Props { struct Item { ... }

    let data: [Item] let reload: Command<Void> let select: Command<Item> } by @DAlooG // 2017 @ GDG Dev Fest
  13. Dispatch closure class ViewController: UIViewController { struct Props { ...

    } enum Action { case reload, select(Item) } var props: Props var dispatch: (Action) -> () } by @DAlooG // 2017 @ GDG Dev Fest
  14. How to design nice Props? → eliminate impossible states with

    types → avoid any sort of identifiers → use flatten props by @DAlooG // 2017 @ GDG Dev Fest
  15. How to eliminate impossible states? struct Props { let isLoading:

    Bool let login: Command? let cancel Command? let setUsername: Command<String>? let setPassword: Command<String>? } by @DAlooG // 2017 @ GDG Dev Fest
  16. How to eliminate impossible states? enum Props { case input(setUsername:

    Command<String>, setPassword: Command<String>, login: Command) case loading(cancel: Command) } by @DAlooG // 2017 @ GDG Dev Fest
  17. How to avoid identifiers? struct Props { struct Item {

    let name: String let id: String } let data: [Item] let select: Command<Item> by @DAlooG // 2017 @ GDG Dev Fest
  18. How to avoid identifiers? struct Props { struct Item {

    let name: String let select: Command } let data: [Item] } by @DAlooG // 2017 @ GDG Dev Fest
  19. How to render complex props? → use enums for modeling

    value exclusivity → use nested types → use phantom types by @DAlooG // 2017 @ GDG Dev Fest
  20. enum Props { case input(Input); struct Input { let username,

    password: Field; struct Field { let value: String let update: Command<String> } let status: Status; enum Status { case invalid, valid(login: Command) } } case loading(cancel: Command) case cancelling } by @DAlooG // 2017 @ GDG Dev Fest
  21. enum UIKitProps { let loginVisible: Bool let loginText: String let

    loginUpdate: Command<String>? ... init(from props: Props) { ... } } by @DAlooG // 2017 @ GDG Dev Fest
  22. class ViewController: UIViewController { var props: Props { didSet {

    uiProps = UIKitProps(from: props) guard isViewLoaded else { return } view.setNeedsLayout() }} by @DAlooG // 2017 @ GDG Dev Fest
  23. class ViewController: UIViewController { override viewWillLayoutSubviews() { login.isHidden = uiProps.loginIsHidden

    ... } @IBAction func login() { uiProps.loginAction?.perform() } } by @DAlooG // 2017 @ GDG Dev Fest
  24. How data-driven controllers can be tested? → snapshot tests →

    interactive storybooks → record/replay by @DAlooG // 2017 @ GDG Dev Fest
  25. How to setup a snapshot tests? class ViewControllerTests: FBSnapshotTestCase {

    func testLoading() { ... } } by @DAlooG // 2017 @ GDG Dev Fest
  26. let vc = ViewController() let window = SnapshotWindow.iPhoneX.landscape vc.props =

    .loading window.rootViewController = vc window.makeKeyAndVisible() FBSnapshotVerifyView(window) by @DAlooG // 2017 @ GDG Dev Fest
  27. Pros of snapshot testing → strong regression tracking → easy

    to review UI related Pull Requests → UI can be fully developed without rest of an app by @DAlooG // 2017 @ GDG Dev Fest
  28. Cons of snapshot testing → a lot of images →

    single bug can break half of images → bad noise/signal ratio → code required for change by @DAlooG // 2017 @ GDG Dev Fest
  29. How to setup interactive storybook? → develop UI in a

    separate framework → run playground → setup props → use LiveView API by @DAlooG // 2017 @ GDG Dev Fest
  30. //UI.playground import PlaygroundSupport @testable import AppUI let vc = ViewController()

    let window = SnapshotWindow.iPhoneX.landscape vc.props = .loading window.rootViewController = vc window.makeKeyAndVisible() PlaygroundPage.current.liveView = window by @DAlooG // 2017 @ GDG Dev Fest
  31. Interactive storybook 2.0 → web based UI for editing props

    → Props should be Codable → WebSockets for syncing between client and host. by @DAlooG // 2017 @ GDG Dev Fest
  32. How to record and replay props? → attach Recorder to

    view controller. → save history by some trigger → send it back to developers → replay by @DAlooG // 2017 @ GDG Dev Fest
  33. struct Recorder<Props: Codable>: Codable { struct Event: Codable { let

    date: Date, let props: Props } var history: [Event] func record(props: Props, date: Date = Date()) { history.append(Event(date: date, props: props)) } func replay(with render: @escaping (Props) -> ()) { ... } by @DAlooG // 2017 @ GDG Dev Fest
  34. Additional Unit testing → converting from Props to UIKitProps →

    invariants of Props by @DAlooG // 2017 @ GDG Dev Fest
  35. How to connect one view controller with another? → using

    storyboard (UI driven) → using some coordinator, pattern (Context driven) by @DAlooG // 2017 @ GDG Dev Fest
  36. Storyboard driven transition struct Props { let data: [Item]; struct

    Item { let name: String let select: Command<DetailsViewController> } } by @DAlooG // 2017 @ GDG Dev Fest
  37. When we should use ui driven transition? → several view

    controllers forms single use case. → we like to keep UI code in the UI :) by @DAlooG // 2017 @ GDG Dev Fest
  38. Coordinator driven transition struct Props { let data: [Item]; struct

    Item { let name: String let select: Command } } by @DAlooG // 2017 @ GDG Dev Fest
  39. What about composition of view controllers? class MasterViewController: UIViewController {

    struct Props { let itemListProps: ItemListViewController.Props let selectedItemProps: DetailsViewController.Props? } } by @DAlooG // 2017 @ GDG Dev Fest
  40. What about animations? class ViewController: UIViewController { func render(props: Props,

    from oldProps: Props, animated: Bool) { ... } } by @DAlooG // 2017 @ GDG Dev Fest
  41. Some hints for animations: → use new property animator API.

    → use view controller transitioning delegates by @DAlooG // 2017 @ GDG Dev Fest
  42. Where I can learn more? → our production code: https://github.com/aol-

    public/OneMobileSDK-controls-ios → my mail: [email protected] → my twitter: @DAlooG → ask questions now by @DAlooG // 2017 @ GDG Dev Fest