Slide 1

Slide 1 text

BENJAMIN ENCZ @BENJAMINENCZ 1 — Turning UIKit Inside Out | @benjaminencz | iOSCon 2016, May 2016

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

*** 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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

"This looks broken!" "Well, it's UI code; what can we do?" 8 — Turning UIKit Inside Out | @benjaminencz | iOSCon 2016, May 2016

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

DECLARATIVE VIEW CODE? render: function() { return (
Seconds Elapsed: {this.state.secondsElapsed}
); 10 — Turning UIKit Inside Out | @benjaminencz | iOSCon 2016, May 2016

Slide 11

Slide 11 text

IMPERATIVE VS. DECLARATIVE 11 — Turning UIKit Inside Out | @benjaminencz | iOSCon 2016, May 2016

Slide 12

Slide 12 text

▸ 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

Slide 13

Slide 13 text

▸ 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

Slide 14

Slide 14 text

THE STATE OF UIKIT 14 — Turning UIKit Inside Out | @benjaminencz | iOSCon 2016, May 2016

Slide 15

Slide 15 text

IS UIKIT DECLARATIVE YET? ✅ (Mostly) Static Individual Views ❌ Dynamic Content 15 — Turning UIKit Inside Out | @benjaminencz | iOSCon 2016, May 2016

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

GITHUB.COM/BEN-G/AUTOTABLE 31 — Turning UIKit Inside Out | @benjaminencz | iOSCon 2016, May 2016

Slide 32

Slide 32 text

CAN WE USE THIS FOR ENTIRE APPS? 32 — Turning UIKit Inside Out | @benjaminencz | iOSCon 2016, May 2016

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

✅ EASY TO REASON ABOUT ✅ WELL ENCAPSULATED ✅ TESTABLE ✅ NOT REDUNDANT 38 — Turning UIKit Inside Out | @benjaminencz | iOSCon 2016, May 2016

Slide 39

Slide 39 text

Generic Declarative View Layer? Hard Wrapper Around Some Components: You can do this! ! 39 — Turning UIKit Inside Out | @benjaminencz | iOSCon 2016, May 2016

Slide 40

Slide 40 text

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