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

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?

Benjamin Encz

May 27, 2016
Tweet

More Decks by Benjamin Encz

Other Decks in Programming

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. 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
  5. *** 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
  6. 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
  7. "This looks broken!" "Well, it's UI code; what can we

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

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

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

    @benjaminencz | iOSCon 2016, May 2016
  11. ▸ 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
  12. ▸ 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
  13. THE STATE OF UIKIT 14 — Turning UIKit Inside Out

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

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

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

    UIKit Inside Out | @benjaminencz | iOSCon 2016, May 2016
  17. 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
  18. 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
  19. 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
  20. TURNING UIKIT INSIDE OUT 21 — Turning UIKit Inside Out

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

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

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

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

    | @benjaminencz | iOSCon 2016, May 2016
  25. 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
  26. 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
  27. 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
  28. CAN WE USE THIS FOR ENTIRE APPS? 32 — Turning

    UIKit Inside Out | @benjaminencz | iOSCon 2016, May 2016
  29. 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
  30. ✅ EASY TO REASON ABOUT ✅ WELL ENCAPSULATED ✅ TESTABLE

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

    can do this! ! 39 — Turning UIKit Inside Out | @benjaminencz | iOSCon 2016, May 2016