SlideShare a Scribd company logo
Writing your App
Swiftly
Sommer Panage
Chorus Fitness
@sommer
Writing Your App Swiftly
Writing Your App Swiftly
Patterns!
Today, in 4 short tales
• Schrödinger's Result
• The Little Layout Engine that Could
• Swiftilocks and the Three View States
• Pete and the Repeated Code
The Demo App
Writing Your App Swiftly
Schrödinger's Result
Code in a box
func getFilms(completion: @escaping ([Film]?, APIError?) -> Void) {
let url = SWAPI.baseURL.appendingPathComponent(Endpoint.films.rawValue)
let task = self.session.dataTask(with: url) { (data, response, error) in
if let data = data {
do {
let jsonObject = try JSONSerialization.jsonObject(with: data, options: [])
if let films = SWAPI.decodeFilms(jsonObject: jsonObject) {
completion(films, nil)
} else {
completion(nil, .decoding)
}
} catch {
completion(nil, .server(originalError: error))
}
} else {
completion(nil, .server(originalError: error!))
}
}
task.resume()
}
What we think is happening…
What's actually
happening…
override func viewDidLoad() {
super.viewDidLoad()
apiClient.getFilms() { films, error in
if let films = films {
// Show film UI
if let error = error {
// Log warning...this is weird
}
} else if let error = error {
// Show error UI
} else {
// No results at all? Show error UI I guess?
}
}
}
Result open source framework by Rob Rix
Model our server interaction as it actually is - success / failure!
public enum Result<T, Error: Swift.Error>: ResultProtocol {
case success(T)
case failure(Error)
}
New, improved code
func getFilms(completion: @escaping (Result<[Film], APIError>) -> Void) {
let task = self.session
.dataTask(with: SWAPI.baseURL.appendingPathComponent(Endpoint.films.rawValue)) { (data, response, error) in
let result = Result(data, failWith: APIError.server(originalError: error!))
.flatMap { data in
Result<Any, AnyError>(attempt: { try JSONSerialization.jsonObject(with: data, options: []) })
.mapError { _ in APIError.decoding }
}
.flatMap { Result(SWAPI.decodeFilms(jsonObject: $0), failWith: .decoding) }
completion(result)
}
task.resume()
}
New, improved code
override func viewDidLoad() {
super.viewDidLoad()
apiClient.getFilms() { result in
switch result {
case .success(let films): print(films) // Show my UI!
case .failure(let error): print(error) // Show some error UI
}
}
}
The Moral of the Story
Using the Result enum allowed us to
• Model the sucess/failure of our server interaction more
correctly
• Thus simplify our view controller code.
The Little Layout Engine that
Could
Old-school
override func layoutSubviews() {
super.layoutSubviews()
// WHY AM I DOING THIS?!?!
}
What about Storyboards and Xibs?
• Working in teams becomes harder because...
• XML diffs
• Merge conflicts?!
• No constants
• Stringly typed identifiers
• Fragile connections
Autolayout: iOS 9+ APIs
init() {
super.init(frame: .zero)
addSubview(tableView)
// Autolayout: Table same size as parent
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
tableView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
}
Autolayout: Cartography by Robb Böhnke
init() {
super.init(frame: .zero)
addSubview(tableView)
// Autolayout: Table same size as parent
constrain(tableView, self) { table, parent in
table.edges == parent.edges
}
}
More Cartography
private let margin: CGFloat = 16
private let episodeLeftPadding: CGFloat = 8
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(episodeLabel)
contentView.addSubview(titleLabel)
constrain(episodeLabel, titleLabel, contentView) { episode, title, parent in
episode.leading == parent.leading + margin
episode.top == parent.top + margin
episode.bottom == parent.bottom - margin
title.leading == episode.trailing + episodeLeftPadding
title.trailing <= parent.trailing - margin
title.centerY == episode.centerY
}
}
The Moral of the Story
Using the Cartography framework harnesses Swift's
operator overloads to make programatic AutoLayout a
breeze!
Swiftilocks and the
Three View States
Swiftilocks and the Three View
States
LOADING
Swiftilocks and the Three View
States
SUCCESS
Swiftilocks and the Three View
States
ERROR
Writing Your App Swiftly
State management with bools
/// MainView.swift
var isLoading: Bool = false {
didSet {
errorView.isHidden = true
loadingView.isHidden = !isLoading
}
}
var isError: Bool = false {
didSet {
errorView.isHidden = !isError
loadingView.isHidden = true
}
}
var items: [MovieItem]? {
didSet {
tableView.reloadData()
}
}
/// MainViewController.swift
override func viewDidLoad() {
super.viewDidLoad()
title = "Star Wars Films"
mainView.isLoading = true
apiClient.getFilms() { result in
DispatchQueue.main.async {
switch result {
case .success(let films):
self.mainView.items = films
.map { MovieItem(episodeID: $0.episodeID, title: $0.title) }
.sorted { $0.0.episodeID < $0.1.episodeID }
self.mainView.isLoading = false
self.mainView.isError = false
case .failure(let error):
self.mainView.isLoading = false
self.mainView.isError = true
}
}
}
}
Too many states!!
Data presence + state?!
Enums to the rescue!
final class MainView: UIView {
enum State {
case loading
case loaded(items: [MovieItem])
case error(message: String)
}
init(state: State) { ... }
// the rest of my class...
}
var state: State {
didSet {
switch state {
case .loading:
items = nil
loadingView.isHidden = false
errorView.isHidden = true
case .error(let message):
print(message)
items = nil
loadingView.isHidden = true
errorView.isHidden = false
case .loaded(let movieItems):
loadingView.isHidden = true
errorView.isHidden = true
items = movieItems
tableView.reloadData()
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
title = "Star Wars Films"
mainView.state = .loading
apiClient.getFilms() { result in
DispatchQueue.main.async {
switch result {
case .success(let films):
let items = films
.map { MovieItem(episodeID: $0.episodeID, title: $0.title) }
.sorted { $0.0.episodeID < $0.1.episodeID }
self.mainView.state = .loaded(items: items)
case .failure(let error):
self.mainView.state = .error(message: "Error: (error.localizedDescription)")
}
}
}
}
The Moral of the Story
Modelling our view state with an enum with
associated values allows us to:
1. Simplify our VC
2. Avoid ambiguous state
3. Centralize our logic
It's better...but...
Pete and the Repeated Code.
Repeated code
var state: State {
didSet {
switch state {
case .loading:
text = nil
loadingView.isHidden = false
errorView.isHidden = true
case .error(let message):
print(message)
text = nil
loadingView.isHidden = true
errorView.isHidden = false
case .loaded(let text):
loadingView.isHidden = true
errorView.isHidden = true
text = text
tableView.reloadData()
}
}
}
Protocols save the day!!
• A shared interface of methods and properties
• Addresses a particular task
• Types adopting protocol need not be related
protocol DataLoading {
associatedtype Data
var state: ViewState<Data> { get set }
var loadingView: LoadingView { get }
var errorView: ErrorView { get }
func update()
}
enum ViewState<Content> {
case loading
case loaded(data: Content)
case error(message: String)
}
Default protocol implementation
extension DataLoading where Self: UIView {
func update() {
switch state {
case .loading:
loadingView.isHidden = false
errorView.isHidden = true
case .error(let error):
loadingView.isHidden = true
errorView.isHidden = false
Log.error(error)
case .loaded:
loadingView.isHidden = true
errorView.isHidden = true
}
}
}
Conforming to DataLoading
1. Provide an errorView variable
2. Provide an loadingView variable
3. Provide a state variable that take some sort of Data
4. Call update() whenever needed
DataLoading in our Main View
final class MainView: UIView, DataLoading {
let loadingView = LoadingView()
let errorView = ErrorView()
var state: ViewState<[MovieItem]> {
didSet {
update() // call update whenever we set our list of Movies
tableView.reloadData()
}
}
DataLoading in our Crawl View
class CrawlView: UIView, DataLoading {
let loadingView = LoadingView()
let errorView = ErrorView()
var state: ViewState<String> {
didSet {
update()
crawlLabel.text = state.data
}
}
The Moral of the Story
Decomposing functionality that is shared by non-
related objects into a protocol helps us
• Avoid duplicated code
• Consolidate our logic into one place
Conclusion
• Result: easily differentiate our success/error pathways
• Cartography: use operator overloading to make code more
readable
• ViewState enum: never have an ambigous view state!
• Protocols: define/decompose shared behaviors in unrelated
types
THANK YOU
Contact Me:
@sommer on Twitter
me@sommerpanage.com

More Related Content

What's hot (20)

PDF
Workshop 10: ECMAScript 6
Visual Engineering
 
PDF
The jQuery Divide
Rebecca Murphey
 
PDF
Workshop 5: JavaScript testing
Visual Engineering
 
PDF
Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)
James Titcumb
 
PDF
Your JavaScript Library
Dmitry Baranovskiy
 
PDF
Decoupling with Design Patterns and Symfony2 DIC
Konstantin Kudryashov
 
PDF
Advanced symfony Techniques
Kris Wallsmith
 
PDF
JavaScript and the AST
Jarrod Overson
 
KEY
Object-Oriented Javascript
kvangork
 
PDF
Min-Maxing Software Costs
Konstantin Kudryashov
 
PDF
Doctrine MongoDB ODM (PDXPHP)
Kris Wallsmith
 
PDF
스위프트를 여행하는 히치하이커를 위한 스타일 안내
Jung Kim
 
PDF
Rust ⇋ JavaScript
Ingvar Stepanyan
 
PDF
JavaScript Promise
Joseph Chiang
 
PDF
A New Baseline for Front-End Devs
Rebecca Murphey
 
PDF
PHP Language Trivia
Nikita Popov
 
PPTX
A Functional Guide to Cat Herding with PHP Generators
Mark Baker
 
PDF
Zepto.js, a jQuery-compatible mobile JavaScript framework in 2K
Thomas Fuchs
 
PDF
Introduction to CQRS and Event Sourcing
Samuel ROZE
 
PDF
Sylius and Api Platform The story of integration
Łukasz Chruściel
 
Workshop 10: ECMAScript 6
Visual Engineering
 
The jQuery Divide
Rebecca Murphey
 
Workshop 5: JavaScript testing
Visual Engineering
 
Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)
James Titcumb
 
Your JavaScript Library
Dmitry Baranovskiy
 
Decoupling with Design Patterns and Symfony2 DIC
Konstantin Kudryashov
 
Advanced symfony Techniques
Kris Wallsmith
 
JavaScript and the AST
Jarrod Overson
 
Object-Oriented Javascript
kvangork
 
Min-Maxing Software Costs
Konstantin Kudryashov
 
Doctrine MongoDB ODM (PDXPHP)
Kris Wallsmith
 
스위프트를 여행하는 히치하이커를 위한 스타일 안내
Jung Kim
 
Rust ⇋ JavaScript
Ingvar Stepanyan
 
JavaScript Promise
Joseph Chiang
 
A New Baseline for Front-End Devs
Rebecca Murphey
 
PHP Language Trivia
Nikita Popov
 
A Functional Guide to Cat Herding with PHP Generators
Mark Baker
 
Zepto.js, a jQuery-compatible mobile JavaScript framework in 2K
Thomas Fuchs
 
Introduction to CQRS and Event Sourcing
Samuel ROZE
 
Sylius and Api Platform The story of integration
Łukasz Chruściel
 

Viewers also liked (20)

PDF
20170302 tryswift tasting_tests
Kazuaki Matsuo
 
PDF
Client-Side Deep Learning
Shuichi Tsutsumi
 
PDF
Swift + GraphQL
Sommer Panage
 
PDF
RxSwift x Realm
Kosuke Usami
 
PDF
描画とビジネスをクリーンに分ける(公開用)
Kenji Tanaka
 
PDF
React Nativeはクロスプラットフォームモバイルアプリ開発の夢を見るか #DroidKaigi
Yukiya Nakagawa
 
PDF
みんなで Swift 復習会での談笑用スライド – 6th #minna_de_swift
Tomohiro Kumagai
 
PDF
Swift イニシャライザー復習会 #love_swift #akibaswift #21cafe
Tomohiro Kumagai
 
PPTX
The Future of Mobile - Presented at SMX Munich
Eric Enge
 
PPTX
Presentazione Bando Pre-Seed
Lazio Innova
 
PDF
minneにおけるテスト〜リリース〜リリース後にやっている事の紹介
Masataka Kono
 
PDF
【掲載用】アウトプットし続ける技術20170314
Hayashi Masayuki
 
PPTX
決定版:サービスの盛り上がり具合をユーザの数(DAU)から読み解く方法
Daisuke Nogami
 
PDF
H2O x mrubyで人はどれだけ幸せになれるのか
Ichito Nagata
 
PPTX
スマホマーケットの概要と、 マーケティングの失敗例と改善 (アナリティクス アソシエーション 特別セミナー)
Tokoroten Nakayama
 
PDF
Design in Tech Report 2017
John Maeda
 
PDF
Boletín 15/03/2017
Openbank
 
PPTX
Personal effectiveness in the workplace
Angela Ihunweze
 
PDF
Gamification Workshop handouts
Bart Hufen
 
PDF
3DCGMeetup08_MayaRigSystem_mGear
ue_ta
 
20170302 tryswift tasting_tests
Kazuaki Matsuo
 
Client-Side Deep Learning
Shuichi Tsutsumi
 
Swift + GraphQL
Sommer Panage
 
RxSwift x Realm
Kosuke Usami
 
描画とビジネスをクリーンに分ける(公開用)
Kenji Tanaka
 
React Nativeはクロスプラットフォームモバイルアプリ開発の夢を見るか #DroidKaigi
Yukiya Nakagawa
 
みんなで Swift 復習会での談笑用スライド – 6th #minna_de_swift
Tomohiro Kumagai
 
Swift イニシャライザー復習会 #love_swift #akibaswift #21cafe
Tomohiro Kumagai
 
The Future of Mobile - Presented at SMX Munich
Eric Enge
 
Presentazione Bando Pre-Seed
Lazio Innova
 
minneにおけるテスト〜リリース〜リリース後にやっている事の紹介
Masataka Kono
 
【掲載用】アウトプットし続ける技術20170314
Hayashi Masayuki
 
決定版:サービスの盛り上がり具合をユーザの数(DAU)から読み解く方法
Daisuke Nogami
 
H2O x mrubyで人はどれだけ幸せになれるのか
Ichito Nagata
 
スマホマーケットの概要と、 マーケティングの失敗例と改善 (アナリティクス アソシエーション 特別セミナー)
Tokoroten Nakayama
 
Design in Tech Report 2017
John Maeda
 
Boletín 15/03/2017
Openbank
 
Personal effectiveness in the workplace
Angela Ihunweze
 
Gamification Workshop handouts
Bart Hufen
 
3DCGMeetup08_MayaRigSystem_mGear
ue_ta
 
Ad

Similar to Writing Your App Swiftly (20)

PDF
Crossing platforms with JavaScript & React
Robert DeLuca
 
PDF
Future of Web Apps: Google Gears
dion
 
PPTX
Javascript first-class citizenery
toddbr
 
PPTX
Adding a modern twist to legacy web applications
Jeff Durta
 
PDF
iOS Talks 6: Unit Testing
Marin Benčević
 
PDF
SQLite and ORM Binding - Part 1 - Transcript.pdf
ShaiAlmog1
 
PDF
Our challenge for Bulkload reliability improvement
Satoshi Akama
 
KEY
Asynchronous Interfaces
maccman
 
ODP
Building a Cloud API Server using Play(SCALA) & Riak
RajthilakMCA
 
PPT
Dojo and Adobe AIR
Nikolai Onken
 
PDF
MCE^3 - Natasha Murashev - Practical Protocol-Oriented Programming in Swift
PROIDEA
 
PDF
Practical Protocol-Oriented-Programming
Natasha Murashev
 
PDF
Practialpop 160510130818
Shahzain Saeed
 
PDF
SproutCore and the Future of Web Apps
Mike Subelsky
 
ODP
Scti 2011 minicurso jquery
ciberglo
 
PDF
Single page webapps & javascript-testing
smontanari
 
PPT
Automated javascript unit testing
ryan_chambers
 
PDF
Swift Delhi: Practical POP
Natasha Murashev
 
PDF
WebAPIs & WebRTC - Spotify/sthlm.js
Robert Nyman
 
PPTX
Adding a modern twist to legacy web applications
Jeff Durta
 
Crossing platforms with JavaScript & React
Robert DeLuca
 
Future of Web Apps: Google Gears
dion
 
Javascript first-class citizenery
toddbr
 
Adding a modern twist to legacy web applications
Jeff Durta
 
iOS Talks 6: Unit Testing
Marin Benčević
 
SQLite and ORM Binding - Part 1 - Transcript.pdf
ShaiAlmog1
 
Our challenge for Bulkload reliability improvement
Satoshi Akama
 
Asynchronous Interfaces
maccman
 
Building a Cloud API Server using Play(SCALA) & Riak
RajthilakMCA
 
Dojo and Adobe AIR
Nikolai Onken
 
MCE^3 - Natasha Murashev - Practical Protocol-Oriented Programming in Swift
PROIDEA
 
Practical Protocol-Oriented-Programming
Natasha Murashev
 
Practialpop 160510130818
Shahzain Saeed
 
SproutCore and the Future of Web Apps
Mike Subelsky
 
Scti 2011 minicurso jquery
ciberglo
 
Single page webapps & javascript-testing
smontanari
 
Automated javascript unit testing
ryan_chambers
 
Swift Delhi: Practical POP
Natasha Murashev
 
WebAPIs & WebRTC - Spotify/sthlm.js
Robert Nyman
 
Adding a modern twist to legacy web applications
Jeff Durta
 
Ad

Recently uploaded (20)

PDF
What Is an Internal Quality Audit and Why It Matters for Your QMS
BizPortals365
 
PPTX
NeuroStrata: Harnessing Neuro-Symbolic Paradigms for Improved Testability and...
Ivan Ruchkin
 
PPTX
B2C EXTRANET | EXTRANET WEBSITE | EXTRANET INTEGRATION
philipnathen82
 
PDF
Automated Testing and Safety Analysis of Deep Neural Networks
Lionel Briand
 
PDF
How DeepSeek Beats ChatGPT: Cost Comparison and Key Differences
sumitpurohit810
 
PDF
Difference Between Kubernetes and Docker .pdf
Kindlebit Solutions
 
PPTX
IObit Driver Booster Pro Crack Download Latest Version
chaudhryakashoo065
 
PDF
AI Software Development Process, Strategies and Challenges
Net-Craft.com
 
PDF
Laboratory Workflows Digitalized and live in 90 days with Scifeon´s SAPPA P...
info969686
 
PPTX
IObit Driver Booster Pro 12.4-12.5 license keys 2025-2026
chaudhryakashoo065
 
PDF
IObit Uninstaller Pro 14.3.1.8 Crack for Windows Latest
utfefguu
 
PDF
Writing Maintainable Playwright Tests with Ease
Shubham Joshi
 
PPTX
computer forensics encase emager app exp6 1.pptx
ssuser343e92
 
PPTX
How Can Recruitment Management Software Improve Hiring Efficiency?
HireME
 
PPTX
For my supp to finally picking supp that work
necas19388
 
PPTX
Seamless-Image-Conversion-From-Raster-to-wrt-rtx-rtx.pptx
Quick Conversion Services
 
PDF
Rewards and Recognition (2).pdf
ethan Talor
 
PPTX
declaration of Variables and constants.pptx
meemee7378
 
PPTX
Introduction to web development | MERN Stack
JosephLiyon
 
PPTX
Automatic_Iperf_Log_Result_Excel_visual_v2.pptx
Chen-Chih Lee
 
What Is an Internal Quality Audit and Why It Matters for Your QMS
BizPortals365
 
NeuroStrata: Harnessing Neuro-Symbolic Paradigms for Improved Testability and...
Ivan Ruchkin
 
B2C EXTRANET | EXTRANET WEBSITE | EXTRANET INTEGRATION
philipnathen82
 
Automated Testing and Safety Analysis of Deep Neural Networks
Lionel Briand
 
How DeepSeek Beats ChatGPT: Cost Comparison and Key Differences
sumitpurohit810
 
Difference Between Kubernetes and Docker .pdf
Kindlebit Solutions
 
IObit Driver Booster Pro Crack Download Latest Version
chaudhryakashoo065
 
AI Software Development Process, Strategies and Challenges
Net-Craft.com
 
Laboratory Workflows Digitalized and live in 90 days with Scifeon´s SAPPA P...
info969686
 
IObit Driver Booster Pro 12.4-12.5 license keys 2025-2026
chaudhryakashoo065
 
IObit Uninstaller Pro 14.3.1.8 Crack for Windows Latest
utfefguu
 
Writing Maintainable Playwright Tests with Ease
Shubham Joshi
 
computer forensics encase emager app exp6 1.pptx
ssuser343e92
 
How Can Recruitment Management Software Improve Hiring Efficiency?
HireME
 
For my supp to finally picking supp that work
necas19388
 
Seamless-Image-Conversion-From-Raster-to-wrt-rtx-rtx.pptx
Quick Conversion Services
 
Rewards and Recognition (2).pdf
ethan Talor
 
declaration of Variables and constants.pptx
meemee7378
 
Introduction to web development | MERN Stack
JosephLiyon
 
Automatic_Iperf_Log_Result_Excel_visual_v2.pptx
Chen-Chih Lee
 

Writing Your App Swiftly

  • 1. Writing your App Swiftly Sommer Panage Chorus Fitness @sommer
  • 5. Today, in 4 short tales • Schrödinger's Result • The Little Layout Engine that Could • Swiftilocks and the Three View States • Pete and the Repeated Code
  • 9. Code in a box func getFilms(completion: @escaping ([Film]?, APIError?) -> Void) { let url = SWAPI.baseURL.appendingPathComponent(Endpoint.films.rawValue) let task = self.session.dataTask(with: url) { (data, response, error) in if let data = data { do { let jsonObject = try JSONSerialization.jsonObject(with: data, options: []) if let films = SWAPI.decodeFilms(jsonObject: jsonObject) { completion(films, nil) } else { completion(nil, .decoding) } } catch { completion(nil, .server(originalError: error)) } } else { completion(nil, .server(originalError: error!)) } } task.resume() }
  • 10. What we think is happening…
  • 11. What's actually happening… override func viewDidLoad() { super.viewDidLoad() apiClient.getFilms() { films, error in if let films = films { // Show film UI if let error = error { // Log warning...this is weird } } else if let error = error { // Show error UI } else { // No results at all? Show error UI I guess? } } }
  • 12. Result open source framework by Rob Rix Model our server interaction as it actually is - success / failure! public enum Result<T, Error: Swift.Error>: ResultProtocol { case success(T) case failure(Error) }
  • 13. New, improved code func getFilms(completion: @escaping (Result<[Film], APIError>) -> Void) { let task = self.session .dataTask(with: SWAPI.baseURL.appendingPathComponent(Endpoint.films.rawValue)) { (data, response, error) in let result = Result(data, failWith: APIError.server(originalError: error!)) .flatMap { data in Result<Any, AnyError>(attempt: { try JSONSerialization.jsonObject(with: data, options: []) }) .mapError { _ in APIError.decoding } } .flatMap { Result(SWAPI.decodeFilms(jsonObject: $0), failWith: .decoding) } completion(result) } task.resume() }
  • 14. New, improved code override func viewDidLoad() { super.viewDidLoad() apiClient.getFilms() { result in switch result { case .success(let films): print(films) // Show my UI! case .failure(let error): print(error) // Show some error UI } } }
  • 15. The Moral of the Story Using the Result enum allowed us to • Model the sucess/failure of our server interaction more correctly • Thus simplify our view controller code.
  • 16. The Little Layout Engine that Could
  • 17. Old-school override func layoutSubviews() { super.layoutSubviews() // WHY AM I DOING THIS?!?! }
  • 18. What about Storyboards and Xibs? • Working in teams becomes harder because... • XML diffs • Merge conflicts?! • No constants • Stringly typed identifiers • Fragile connections
  • 19. Autolayout: iOS 9+ APIs init() { super.init(frame: .zero) addSubview(tableView) // Autolayout: Table same size as parent tableView.translatesAutoresizingMaskIntoConstraints = false tableView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true tableView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true tableView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true tableView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true }
  • 20. Autolayout: Cartography by Robb Böhnke init() { super.init(frame: .zero) addSubview(tableView) // Autolayout: Table same size as parent constrain(tableView, self) { table, parent in table.edges == parent.edges } }
  • 21. More Cartography private let margin: CGFloat = 16 private let episodeLeftPadding: CGFloat = 8 override init(style: UITableViewCellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) contentView.addSubview(episodeLabel) contentView.addSubview(titleLabel) constrain(episodeLabel, titleLabel, contentView) { episode, title, parent in episode.leading == parent.leading + margin episode.top == parent.top + margin episode.bottom == parent.bottom - margin title.leading == episode.trailing + episodeLeftPadding title.trailing <= parent.trailing - margin title.centerY == episode.centerY } }
  • 22. The Moral of the Story Using the Cartography framework harnesses Swift's operator overloads to make programatic AutoLayout a breeze!
  • 24. Swiftilocks and the Three View States LOADING
  • 25. Swiftilocks and the Three View States SUCCESS
  • 26. Swiftilocks and the Three View States ERROR
  • 28. State management with bools /// MainView.swift var isLoading: Bool = false { didSet { errorView.isHidden = true loadingView.isHidden = !isLoading } } var isError: Bool = false { didSet { errorView.isHidden = !isError loadingView.isHidden = true } } var items: [MovieItem]? { didSet { tableView.reloadData() } }
  • 29. /// MainViewController.swift override func viewDidLoad() { super.viewDidLoad() title = "Star Wars Films" mainView.isLoading = true apiClient.getFilms() { result in DispatchQueue.main.async { switch result { case .success(let films): self.mainView.items = films .map { MovieItem(episodeID: $0.episodeID, title: $0.title) } .sorted { $0.0.episodeID < $0.1.episodeID } self.mainView.isLoading = false self.mainView.isError = false case .failure(let error): self.mainView.isLoading = false self.mainView.isError = true } } } }
  • 31. Data presence + state?!
  • 32. Enums to the rescue! final class MainView: UIView { enum State { case loading case loaded(items: [MovieItem]) case error(message: String) } init(state: State) { ... } // the rest of my class... }
  • 33. var state: State { didSet { switch state { case .loading: items = nil loadingView.isHidden = false errorView.isHidden = true case .error(let message): print(message) items = nil loadingView.isHidden = true errorView.isHidden = false case .loaded(let movieItems): loadingView.isHidden = true errorView.isHidden = true items = movieItems tableView.reloadData() } } }
  • 34. override func viewDidLoad() { super.viewDidLoad() title = "Star Wars Films" mainView.state = .loading apiClient.getFilms() { result in DispatchQueue.main.async { switch result { case .success(let films): let items = films .map { MovieItem(episodeID: $0.episodeID, title: $0.title) } .sorted { $0.0.episodeID < $0.1.episodeID } self.mainView.state = .loaded(items: items) case .failure(let error): self.mainView.state = .error(message: "Error: (error.localizedDescription)") } } } }
  • 35. The Moral of the Story Modelling our view state with an enum with associated values allows us to: 1. Simplify our VC 2. Avoid ambiguous state 3. Centralize our logic
  • 37. Pete and the Repeated Code.
  • 38. Repeated code var state: State { didSet { switch state { case .loading: text = nil loadingView.isHidden = false errorView.isHidden = true case .error(let message): print(message) text = nil loadingView.isHidden = true errorView.isHidden = false case .loaded(let text): loadingView.isHidden = true errorView.isHidden = true text = text tableView.reloadData() } } }
  • 39. Protocols save the day!! • A shared interface of methods and properties • Addresses a particular task • Types adopting protocol need not be related
  • 40. protocol DataLoading { associatedtype Data var state: ViewState<Data> { get set } var loadingView: LoadingView { get } var errorView: ErrorView { get } func update() }
  • 41. enum ViewState<Content> { case loading case loaded(data: Content) case error(message: String) }
  • 42. Default protocol implementation extension DataLoading where Self: UIView { func update() { switch state { case .loading: loadingView.isHidden = false errorView.isHidden = true case .error(let error): loadingView.isHidden = true errorView.isHidden = false Log.error(error) case .loaded: loadingView.isHidden = true errorView.isHidden = true } } }
  • 43. Conforming to DataLoading 1. Provide an errorView variable 2. Provide an loadingView variable 3. Provide a state variable that take some sort of Data 4. Call update() whenever needed
  • 44. DataLoading in our Main View final class MainView: UIView, DataLoading { let loadingView = LoadingView() let errorView = ErrorView() var state: ViewState<[MovieItem]> { didSet { update() // call update whenever we set our list of Movies tableView.reloadData() } }
  • 45. DataLoading in our Crawl View class CrawlView: UIView, DataLoading { let loadingView = LoadingView() let errorView = ErrorView() var state: ViewState<String> { didSet { update() crawlLabel.text = state.data } }
  • 46. The Moral of the Story Decomposing functionality that is shared by non- related objects into a protocol helps us • Avoid duplicated code • Consolidate our logic into one place
  • 47. Conclusion • Result: easily differentiate our success/error pathways • Cartography: use operator overloading to make code more readable • ViewState enum: never have an ambigous view state! • Protocols: define/decompose shared behaviors in unrelated types