Turning UIKit Inside Out

Turning UIKit Inside Out

UIKit is an extremely powerful framework that has enabled developers to build sophisticated apps throughout the last couple of years. However, UIKit's architecture has not kept up with the changes in the iOS developer community, which are mostly driven by the introduction of Swift. Swift developers tend to favor a declarative programming style, UIKit requires the opposite. UIKit mostly works on delegates and callback blocks that provide data to the framework and influence what is rendered on the screen. Forcing our Swift code into this architecture often results in code that is difficult to understand & maintain. Further, UIKit makes it unfortunately easy to write bad code. What if we could turn this inside out? What if the view layer was simply a mapping of our data layer, not requiring delegation or callbacks?

De23af005c790b22f8ce4d201e6ca027?s=128

Benjamin Encz

May 27, 2016
Tweet

Transcript

  1. BENJAMIN ENCZ @BENJAMINENCZ 1 — Turning UIKit Inside Out |

    @benjaminencz | iOSCon 2016, May 2016
  2. TURNING UIKIT INSIDE OUT 2 — Turning UIKit Inside Out

    | @benjaminencz | iOSCon 2016, May 2016
  3. ❤+⚔ VIEW PROGRAMMING 3 — Turning UIKit Inside Out |

    @benjaminencz | iOSCon 2016, May 2016
  4. 4 — Turning UIKit Inside Out | @benjaminencz | iOSCon

    2016, May 2016
  5. AGENDA 1. Issues With UI Programming 2. Imperative vs. Declarative

    UIs 3. State of UI programming with UIKit 4. Build Your Own Declarative View Layer 5. Declarative UIs Everywhere? 5 — Turning UIKit Inside Out | @benjaminencz | iOSCon 2016, May 2016
  6. *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid

    update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (2) must be equal to the number of rows contained in that section before the update (3), plus or minus the number of rows inserted or deleted from that section (0 inserted, 2 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).' 6 — Turning UIKit Inside Out | @benjaminencz | iOSCon 2016, May 2016
  7. INTERACTIVE UIS WITH IMPERATIVE CODE 1. Build Initial UI 2.

    Update App State 3. Poke at UI to Update it 7 — Turning UIKit Inside Out | @benjaminencz | iOSCon 2016, May 2016
  8. "This looks broken!" "Well, it's UI code; what can we

    do?" 8 — Turning UIKit Inside Out | @benjaminencz | iOSCon 2016, May 2016
  9. IMPERATIVE VS. DECLARATIVE VIEW CODE 9 — Turning UIKit Inside

    Out | @benjaminencz | iOSCon 2016, May 2016
  10. DECLARATIVE VIEW CODE? render: function() { return ( <div>Seconds Elapsed:

    {this.state.secondsElapsed}</div> ); 10 — Turning UIKit Inside Out | @benjaminencz | iOSCon 2016, May 2016
  11. IMPERATIVE VS. DECLARATIVE 11 — Turning UIKit Inside Out |

    @benjaminencz | iOSCon 2016, May 2016
  12. ▸ Hard to Reason About, Hard to Test ▸ Defers

    Complexity to the User ▸ Encourages Coupling ▸ View Code Can Have Arbitrary Side Effects 12 — Turning UIKit Inside Out | @benjaminencz | iOSCon 2016, May 2016
  13. ▸ View Code Can Only Map From State to View

    ▸ Easy to Test: Provide Input, Assert Output ▸ Defers Complexity to the System 13 — Turning UIKit Inside Out | @benjaminencz | iOSCon 2016, May 2016
  14. THE STATE OF UIKIT 14 — Turning UIKit Inside Out

    | @benjaminencz | iOSCon 2016, May 2016
  15. IS UIKIT DECLARATIVE YET? ✅ (Mostly) Static Individual Views ❌

    Dynamic Content 15 — Turning UIKit Inside Out | @benjaminencz | iOSCon 2016, May 2016
  16. API EXAMPLE: UITABLEVIEW 16 — Turning UIKit Inside Out |

    @benjaminencz | iOSCon 2016, May 2016
  17. Who loves implementing UITableViewDataSource for a living? 17 — Turning

    UIKit Inside Out | @benjaminencz | iOSCon 2016, May 2016
  18. let userSection = 1 let storySection = 2 let settingSection

    = 3 func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let tableViewCell: UITableViewCell switch indexPath.section { case userSection: tableViewCell = tableView.dequeueReusableCellWithIdentifier("user")! // ... case storySection: tableViewCell = tableView.dequeueReusableCellWithIdentifier("story")! // ... case settingSection: tableViewCell = tableView.dequeueReusableCellWithIdentifier("setting")! // ... default: fatalError("No cell for indexPath: \(indexPath)") } return tableViewCell } 18 — Turning UIKit Inside Out | @benjaminencz | iOSCon 2016, May 2016
  19. override func tableView(tableView: UITableView, shouldHighlightRowAtIndexPath indexPath: NSIndexPath) -> Bool {

    switch indexPath.row { case UserAction.Status.rawValue: return self.canChangeStatus || self.canClose case UserAction.Assignee.rawValue, UserAction.Phone.rawValue, UserAction.Title.rawValue, UserAction.Description.rawValue: return self.canEdit case UserAction.Shared.rawValue: return self.canShare && !self.allUsersShared case UserAction.RemoveMaster.rawValue: return self.canUnshare && !self.allUsersNotShared case UserAction.Archive.rawValue: return self.canDelete default: return true } } 19 — Turning UIKit Inside Out | @benjaminencz | iOSCon 2016, May 2016
  20. We're taking a declarative description of what we want to

    see on the screen and are turning it into a sequence of imperative statements, just so UIKit can understand us. 20 — Turning UIKit Inside Out | @benjaminencz | iOSCon 2016, May 2016
  21. TURNING UIKIT INSIDE OUT 21 — Turning UIKit Inside Out

    | @benjaminencz | iOSCon 2016, May 2016
  22. BUILD YOUR OWN DECLARATIVE VIEW LAYER 22 — Turning UIKit

    Inside Out | @benjaminencz | iOSCon 2016, May 2016
  23. EXISTING SOLUTIONS ▸ React Native ▸ ComponentKit ▸ Few.swift (experimental)

    23 — Turning UIKit Inside Out | @benjaminencz | iOSCon 2016, May 2016
  24. NO PRODUCTION READY SOLUTION FOR DO IT YOURSELF 24 —

    Turning UIKit Inside Out | @benjaminencz | iOSCon 2016, May 2016
  25. (STATE) -> VIEW TREE 25 — Turning UIKit Inside Out

    | @benjaminencz | iOSCon 2016, May 2016
  26. 26 — Turning UIKit Inside Out | @benjaminencz | iOSCon

    2016, May 2016
  27. var users: [User] = [ User(username: "A"), User(username: "B"), User(username:

    "C"), User(username: "D") ] override func viewDidLoad() { self.tableViewRenderer = TableViewShim(cellTypes: [ CellTypeDefinition( nibFilename: "UserCell", cellIdentifier: "UserCell" )], tableView: tableView) self.tableViewRenderer.tableViewModel = tableViewModelForUserList( users, deleteClosure: deleteUser ) } 27 — Turning UIKit Inside Out | @benjaminencz | iOSCon 2016, May 2016
  28. func tableViewModelForUserList(users: [User], deleteClosure: CommitEditingClosure) -> TableViewModel { return TableViewModel(sections:

    [ TableViewSectionModel(cells: users.map { viewModelForUser($0, deleteClosure: deleteClosure) } ) ]) } func viewModelForUser(user: User, deleteClosure: CommitEditingClosure) -> TableViewCellModel { func applyViewModelToCell(cell: UITableViewCell, user: Any) { guard let cell = cell as? UserCell else { return } guard let user = user as? User else { return } cell.nameLabel.text = user.username } return TableViewCellModel( cellIdentifier: "UserCell", applyViewModelToCell: applyViewModelToCell, commitEditingClosure: deleteClosure, customData: user ) } 28 — Turning UIKit Inside Out | @benjaminencz | iOSCon 2016, May 2016
  29. func deleteUser(indexPath: NSIndexPath) { self.users.removeAtIndex(indexPath.row) self.tableViewRenderer.newViewModelWithChangeset( tableViewModelForUserList( users, deleteClosure: deleteUser

    ), changeSet: .Delete(indexPath) ) } 29 — Turning UIKit Inside Out | @benjaminencz | iOSCon 2016, May 2016
  30. TableViewModel( sections: [ TableViewSectionModel( cells: users.map(cellModelForUser(actionHandler)) ), TableViewSectionModel( cells: stories.map(storyModelForUser(actionHandler))

    ) ] ) vs. 100s of line of UITableViewDataSource 30 — Turning UIKit Inside Out | @benjaminencz | iOSCon 2016, May 2016
  31. GITHUB.COM/BEN-G/AUTOTABLE 31 — Turning UIKit Inside Out | @benjaminencz |

    iOSCon 2016, May 2016
  32. CAN WE USE THIS FOR ENTIRE APPS? 32 — Turning

    UIKit Inside Out | @benjaminencz | iOSCon 2016, May 2016
  33. DEMO 33 — Turning UIKit Inside Out | @benjaminencz |

    iOSCon 2016, May 2016
  34. IMPLEMENTATION DETAILS ▸ Components Map To Views ▸ Components Have

    Platform Specific Renderers ▸ Renderers Understand How to Update With New Component State ▸ Library relies on Dwifft to Diff Two Component Trees 34 — Turning UIKit Inside Out | @benjaminencz | iOSCon 2016, May 2016
  35. 35 — Turning UIKit Inside Out | @benjaminencz | iOSCon

    2016, May 2016
  36. ⚠⚠GitHub.com/Ben-G/UILib⚠⚠ ⚠GitHub.com/joshaber/Few.swift⚠ 36 — Turning UIKit Inside Out | @benjaminencz

    | iOSCon 2016, May 2016
  37. CONCLUSION 37 — Turning UIKit Inside Out | @benjaminencz |

    iOSCon 2016, May 2016
  38. ✅ EASY TO REASON ABOUT ✅ WELL ENCAPSULATED ✅ TESTABLE

    ✅ NOT REDUNDANT 38 — Turning UIKit Inside Out | @benjaminencz | iOSCon 2016, May 2016
  39. Generic Declarative View Layer? Hard Wrapper Around Some Components: You

    can do this! ! 39 — Turning UIKit Inside Out | @benjaminencz | iOSCon 2016, May 2016
  40. THANK YOU! bit.ly/uikit-inside-out github.com/Ben-G/AutoTable github.com/Ben-G/UILib github.com/joshaber/Few.swift @BENJAMINENCZ 40 — Turning

    UIKit Inside Out | @benjaminencz | iOSCon 2016, May 2016