Slide 1

Slide 1 text

VIEW-STATE DRIVEN APPLICATIONS MATT GALLAGHER, TRY! SWIFT NYC, 2017

Slide 2

Slide 2 text

@cocoawithlove https://www.cocoawithlove.com

Slide 3

Slide 3 text

MATT GALLAGHER, TRY! SWIFT NYC 2017: VIEW-STATE DRIVEN APPLICATIONS ONE GOAL, THREE QUESTIONS ▸ Goal: think about view-state. ▸ What is view-state? ▸ How is view-state treated in standard Cocoa apps? ▸ What happens if we take this (mostly hidden) data and use it to drive the whole application?

Slide 4

Slide 4 text

MATT GALLAGHER, TRY! SWIFT NYC 2017: VIEW-STATE DRIVEN APPLICATIONS THE COCOA APPLICATION DESIGN PATTERN ▸ Described as "Model-View-Controller" CONTROLLER VIEW MODEL

Slide 5

Slide 5 text

MATT GALLAGHER, TRY! SWIFT NYC 2017: VIEW-STATE DRIVEN APPLICATIONS IDEAL MODEL-VIEW-CONTROLLER CHANGE PATTERN VIEW CONTROLLER MODEL OBSERVER CONTROLLER VIEW Runtime binding Compile-time binding Model context

Slide 6

Slide 6 text

MATT GALLAGHER, TRY! SWIFT NYC 2017: VIEW-STATE DRIVEN APPLICATIONS REASONS FOR DATA-DRIVEN PATTERNS ▸ Clean interface on the model makes testing easier ▸ Views can be coordinated by depending on common state ▸ Reduces the need for knowledge ▸ And because all model changes happen on the same path, we can do interesting things…

Slide 7

Slide 7 text

MATT GALLAGHER, TRY! SWIFT NYC 2017: VIEW-STATE DRIVEN APPLICATIONS DEMO CLOCKS, PART 1

Slide 8

Slide 8 text

MATT GALLAGHER, TRY! SWIFT NYC 2017: VIEW-STATE DRIVEN APPLICATIONS TWO SETS OF RULES ▸ "Document" data – the “model” – uses a data-driven pipeline ▸ "View-state" uses a presentation-driven pipeline

Slide 9

Slide 9 text

Model context MATT GALLAGHER, TRY! SWIFT NYC 2017: VIEW-STATE DRIVEN APPLICATIONS INSTEAD OF THIS DATA-DRIVEN PATTERN… VIEW CONTROLLER MODEL OBSERVER CONTROLLER VIEW VIEW CONTROLLER CONTROLLER WE HAVE THIS PRESENTATION-DRIVEN PATTERN… VIEW

Slide 10

Slide 10 text

MATT GALLAGHER, TRY! SWIFT NYC 2017: VIEW-STATE DRIVEN APPLICATIONS CHANGING DETAIL VIEW (PRESENTATION-DRIVEN) override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { performSegue(withIdentifier: "showDetail", sender: self) } override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == "showDetail" { if let indexPath = tableView.indexPathForSelectedRow { let timezone = sortedTimezones[indexPath.row] let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController controller.timezone = timezone } } } override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { if editingStyle == .delete { Document.shared.removeObject(sortedObjects[indexPath.row]) } } @objc func handleChangeNotification(_ notification: Notification) { sortedObjects = Document.shared.objectsSortedByKey(sortKey) tableView.reloadData() } DELETING A ROW (DATA-DRIVEN)

Slide 11

Slide 11 text

MATT GALLAGHER, TRY! SWIFT NYC 2017: VIEW-STATE DRIVEN APPLICATIONS WHAT ARE THE CONSEQUENCES? ▸ Total coupling between cause and effect ▸ Difficult to add additional dependencies (each additional dependency requires manual propagation) ▸ Controllers need to inter-communicate to send segue data ▸ No clean interface for testing or other hooks

Slide 12

Slide 12 text

MATT GALLAGHER, TRY! SWIFT NYC 2017: VIEW-STATE DRIVEN APPLICATIONS DOUBLE STANDARD ▸ There are good reasons why we use a “data driven” approach for model data. ▸ Why do we fail to apply these same techniques to view- state? Is view-state really any different? ▸ Both are critical to our apps. Both should be tested. Both should be persisted.

Slide 13

Slide 13 text

MATT GALLAGHER, TRY! SWIFT NYC 2017: VIEW-STATE DRIVEN APPLICATIONS EVERYTHING MUTABLE IN YOUR APP IS A MODEL ▸ View-state is a model, changes to view-state should take a data-driven path. ▸ Let's identify the view-state in the app and make it into its own model.

Slide 14

Slide 14 text

MATT GALLAGHER, TRY! SWIFT NYC 2017: VIEW-STATE DRIVEN APPLICATIONS LET'S LOOK AT THE DATA MODEL class Document: NotifyingStore { let url: URL private (set) var timezones: [UUID: Timezone] = [:] } struct Timezone: Codable { var name: String let identifier: String let uuid: UUID } ▸ The most important consideration is: what is mutable?

Slide 15

Slide 15 text

MATT GALLAGHER, TRY! SWIFT NYC 2017: VIEW-STATE DRIVEN APPLICATIONS INSPECT MUTABLE ASPECTS OF VIEW HIERARCHY

Slide 16

Slide 16 text

MATT GALLAGHER, TRY! SWIFT NYC 2017: VIEW-STATE DRIVEN APPLICATIONS A REPRESENTATION OF VIEW-STATE struct SplitViewState: Codable { var masterView: MasterViewState var detailView: DetailViewState? var selectionView: SelectionViewState? } struct MasterViewState: Codable { var masterScrollOffsetY: Double = 0 var isEditing: Bool = false } struct DetailViewState: Codable { let uuid: UUID } struct SelectionViewState: Codable { var selectionScrollOffsetY: Double = 0 var searchText: String = "" } ▸ Combined with the 10 lines of data model, we now have a 30 line abstract representation of the whole 600 line app.

Slide 17

Slide 17 text

MATT GALLAGHER, TRY! SWIFT NYC 2017: VIEW-STATE DRIVEN APPLICATIONS VIEW-STATE DRIVEN (DATA-DRIVEN) override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { ViewState.shared.setDetailSelection(uuid: sortedTimezones[indexPath.row].uuid) } @objc func handleViewStateNotification(_ notification: Notification) { let detailView = ViewState.shared.topLevel.detailView if let uuid = detailView?.uuid, Document.shared.timezones[uuid] != nil, lastPresentedUuid != uuid { lastPresentedUuid = uuid performSegue(withIdentifier: notification.userActionData != nil ? "detail" : "detailWithoutAnimation", sender: self) } } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { performSegue(withIdentifier: "showDetail", sender: self) } override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == "showDetail" { if let indexPath = tableView.indexPathForSelectedRow { let timezone = sortedTimezones[indexPath.row] let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController controller.timezone = timezone } } } CHANGING DETAIL VIEW (PRESENTATION-DRIVEN)

Slide 18

Slide 18 text

MATT GALLAGHER, TRY! SWIFT NYC 2017: VIEW-STATE DRIVEN APPLICATIONS DEMO CLOCKS, PART 2

Slide 19

Slide 19 text

MATT GALLAGHER, TRY! SWIFT NYC 2017: VIEW-STATE DRIVEN APPLICATIONS TIME TRAVEL ▸ Time travel is common in "unidirectional data flow" approaches like ReSwift but those architectures involve radical changes to the whole app. ▸ A view-state driven approach doesn’t require a radical rethink of architecture, it just involves treating more of your app like the mode. ▸ The Clocks sample app changes very little from Apple’s Master-Detail app template.

Slide 20

Slide 20 text

MATT GALLAGHER, TRY! SWIFT NYC 2017: VIEW-STATE DRIVEN APPLICATIONS TESTABILITY ▸ User-interfaces don't provide a good code interface to test. Models are abstract, can be isolated and tested. ▸ I don’t just mean “unit testing”, I mean logging application state: {“selectionView":{"selectionScrollOffsetY": 1669,"searchText":"Melbourne"},"masterView":{"masterScrollOffsetY": 688,"isEditing":false}} ▸ This lets us answer the question of “what just happened”? ▸ It also lets us recreate what just happened so we can fix the problem. ▸ Transitions between two arbitrary states also becomes simple.

Slide 21

Slide 21 text

MATT GALLAGHER, TRY! SWIFT NYC 2017: VIEW-STATE DRIVEN APPLICATIONS VIEW-STATE DATA-DRIVEN CHANGE PATTERN VIEW CONTROLLER VIEW-STATE MODEL OBSERVER CONTROLLER VIEW Runtime binding Compile-time binding Model context

Slide 22

Slide 22 text

MATT GALLAGHER, TRY! SWIFT NYC 2017: VIEW-STATE DRIVEN APPLICATIONS DRAWBACKS ▸ Slightly more code because everything needs a change pipeline rather than direct coupling. ▸ Most built-in view controllers need a little cajoling to give up their view state. ▸ Clocks app shows an approach for navigation stack, split views, scroll positions.

Slide 23

Slide 23 text

MATT GALLAGHER, TRY! SWIFT NYC 2017: VIEW-STATE DRIVEN APPLICATIONS HOW DID WE GO? ▸ Goal was to think about view-state. ▸ View-state is anything mutable in a view that isn’t written to the normal “document” model. ▸ Standard Cocoa apps treat it as a side-effect of presentation. ▸ We can treat presentation as a consequence of view-state.

Slide 24

Slide 24 text

MATT GALLAGHER, TRY! SWIFT NYC 2017: VIEW-STATE DRIVEN APPLICATIONS VIEW THE "CLOCKS" SAMPLE APP ▸ https://github.com/mattgallagher/Clocks FIND MORE ▸ Web: https://www.cocoawithlove.com ▸ Twitter: @cocoawithlove