Slide 1

Slide 1 text

let swift == val kotlin → WHY IOS IS SO IMPORTANT FOR ANDROID, AND VICE VERSA Jorge Coca

Slide 2

Slide 2 text

LET'S BUILD A NEW startup

Slide 3

Slide 3 text

la la land DROIDCON NYC EDITION

Slide 4

Slide 4 text

LA LA LAND DROIDCON EDITION ▸ Cast ▸ Songs ▸ Song details ▸ Mark song as favorite

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

!

Slide 7

Slide 7 text

MODERN DEVELOPMENT OPTIONS

Slide 8

Slide 8 text

MODERN DEVELOPMENT OPTIONS ▸ Native

Slide 9

Slide 9 text

MODERN DEVELOPMENT OPTIONS ▸ Native ▸ Progressive Web App

Slide 10

Slide 10 text

MODERN DEVELOPMENT OPTIONS ▸ Native ▸ Progressive Web App ▸ React Native

Slide 11

Slide 11 text

MODERN DEVELOPMENT OPTIONS ▸ Native ▸ Progressive Web App ▸ React Native ▸ Flutter

Slide 12

Slide 12 text

Our team decides to create a native iOS and Android app, but...

Slide 13

Slide 13 text

... we want to share our architecture between plaforms...

Slide 14

Slide 14 text

... and, if possible, we do not want to write the same business logic twice

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

WHY NOT?

Slide 17

Slide 17 text

WHY NOT? ▸ Particular to our app/use case

Slide 18

Slide 18 text

WHY NOT? ▸ Particular to our app/use case ▸ Time

Slide 19

Slide 19 text

WHY NOT? ▸ Particular to our app/use case ▸ Time ▸ Money

Slide 20

Slide 20 text

WHY NOT? ▸ Particular to our app/use case ▸ Time ▸ Money ▸ (Du)plicated efforts

Slide 21

Slide 21 text

THAT SOUNDS AWESOME, BUT...

Slide 22

Slide 22 text

CAN WE DO IT?

Slide 23

Slide 23 text

!

Slide 24

Slide 24 text

MULTIPLATFORM EFFORTS PRINCIPLES Isolate your business logic from: - Presentation - Operating system

Slide 25

Slide 25 text

MULTIPLATFORM EFFORTS PRINCIPLES Isolate your business logic from: - Presentation - Operating system ▸ Also known as layers, clean code, the onion model...

Slide 26

Slide 26 text

No content

Slide 27

Slide 27 text

Sooner than we think, multiplatform developers will be on very high demand — Jorge Coca, visionary

Slide 28

Slide 28 text

WHAT ARE WE GOING TO ACHIEVE IN THIS TALK?

Slide 29

Slide 29 text

WHAT ARE WE GOING TO ACHIEVE IN THIS TALK? ▸ Intro to iOS development

Slide 30

Slide 30 text

WHAT ARE WE GOING TO ACHIEVE IN THIS TALK? ▸ Intro to iOS development ▸ Xcode

Slide 31

Slide 31 text

WHAT ARE WE GOING TO ACHIEVE IN THIS TALK? ▸ Intro to iOS development ▸ Xcode ▸ Swift

Slide 32

Slide 32 text

WHAT ARE WE GOING TO ACHIEVE IN THIS TALK? ▸ Intro to iOS development ▸ Xcode ▸ Swift ▸ UI design with Storyboards

Slide 33

Slide 33 text

WHAT ARE WE GOING TO ACHIEVE IN THIS TALK? ▸ Intro to iOS development ▸ Xcode ▸ Swift ▸ UI design with Storyboards ▸ ... from the point of view of an Android dev

Slide 34

Slide 34 text

LET'S GET STARTED!

Slide 35

Slide 35 text

LANGUAGES & TOOLS ANDROID IOS Android Studio Xcode Kotlin Swift Java Objective-C

Slide 36

Slide 36 text

ANDROID IOS XML Layout Storyboard Activity/Fragment/ CustomView UIViewController BottomNavigationBar TabBarController RecyclerView with GridLayoutManager UICollectionView ImageView UIImageView TextView UILabel

Slide 37

Slide 37 text

No content

Slide 38

Slide 38 text

THERE'S A TON OF DRAG & DROP IN STORYBOARDS

Slide 39

Slide 39 text

UIVIEWCONTROLLER.SWIFT import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } }

Slide 40

Slide 40 text

UIVIEWCONTROLLER.SWIFT import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } }

Slide 41

Slide 41 text

UIVIEWCONTROLLER.SWIFT import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } }

Slide 42

Slide 42 text

UIVIEWCONTROLLER.SWIFT import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } }

Slide 43

Slide 43 text

UIVIEWCONTROLLER.SWIFT import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } }

Slide 44

Slide 44 text

EASY, RIGHT?

Slide 45

Slide 45 text

UIVIEWCONTROLLER.SWIFT import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } }

Slide 46

Slide 46 text

UIKIT?

Slide 47

Slide 47 text

UIKIT? ▸ Cocoa Touch is a framework for developing iOS applications.

Slide 48

Slide 48 text

UIKIT? ▸ Cocoa Touch is a framework for developing iOS applications. ▸ UIKit for UI components (buttons, labels)

Slide 49

Slide 49 text

UIKIT? ▸ Cocoa Touch is a framework for developing iOS applications. ▸ UIKit for UI components (buttons, labels) ▸ Foundation (NSString, NSObject) is the base framework for Apple products

Slide 50

Slide 50 text

... it's like the Android SDK and the Support Libraries and the language, all in one...

Slide 51

Slide 51 text

LET'S ADD THE NAVIGATION CONTROLLER AND THE TAB BAR CONTROLLER

Slide 52

Slide 52 text

No content

Slide 53

Slide 53 text

... WAIT, DO WE HAVE CONTROLLERS AND VIEWCONTROLLERS? ▸ MVC is the default architectural pattern

Slide 54

Slide 54 text

WHERE IS MY Adapter AND MY ViewHolder? protocol is the equivalent of an interface protocol extensions as Kotlin extensions

Slide 55

Slide 55 text

WHERE IS MY Adapter AND MY ViewHolder? ▸ UICollectionView is like a RecyclerView, but for grids protocol is the equivalent of an interface protocol extensions as Kotlin extensions

Slide 56

Slide 56 text

WHERE IS MY Adapter AND MY ViewHolder? ▸ UICollectionView is like a RecyclerView, but for grids ▸ UICollectionViewDataSource is the protocol that owns the data protocol is the equivalent of an interface protocol extensions as Kotlin extensions

Slide 57

Slide 57 text

WHERE IS MY Adapter AND MY ViewHolder? ▸ UICollectionView is like a RecyclerView, but for grids ▸ UICollectionViewDataSource is the protocol that owns the data ▸ UICollectionViewDelegate is the protocol that owns the interaction protocol is the equivalent of an interface protocol extensions as Kotlin extensions

Slide 58

Slide 58 text

WHERE IS MY Adapter AND MY ViewHolder? ▸ UICollectionView is like a RecyclerView, but for grids ▸ UICollectionViewDataSource is the protocol that owns the data ▸ UICollectionViewDelegate is the protocol that owns the interaction ▸ UICollectionViewCell is the UI representation of a single item protocol is the equivalent of an interface protocol extensions as Kotlin extensions

Slide 59

Slide 59 text

ARTISTVIEWCELL.SWIFT import UIKit class ArtistViewCell : UICollectionViewCell { @IBOutlet var headshotImage: UIImageView! @IBOutlet var artistName: UILabel! func displayContent(headshot: UIImage, name: String) { headshotImage.image = headshot artistName.text = name } }

Slide 60

Slide 60 text

ARTISTVIEWCELL.SWIFT import UIKit class ArtistViewCell : UICollectionViewCell { @IBOutlet var headshotImage: UIImageView! @IBOutlet var artistName: UILabel! func displayContent(headshot: UIImage, name: String) { headshotImage.image = headshot artistName.text = name } }

Slide 61

Slide 61 text

@IBOUTLET It is a keyword added to a variable declaration. It binds your code variable to a UI element in the storyboard

Slide 62

Slide 62 text

var AND let/val KOTLIN SWIFT MUTABLE? var var Yes val let No

Slide 63

Slide 63 text

@IBACTION It is a keyword added to a method declaration. It binds your code method to a UI action in the storyboard

Slide 64

Slide 64 text

IMPLICITLY UNWRAPPED OPTIONALS import UIKit class ArtistViewCell : UICollectionViewCell { @IBOutlet var headshotImage: UIImageView! @IBOutlet var artistName: UILabel! func displayContent(artist: Artist) { headshotImage.image = artist.headshot artistName.text = artist.name } }

Slide 65

Slide 65 text

IMPLICITLY UNWRAPPED OPTIONALS import UIKit class ArtistViewCell : UICollectionViewCell { @IBOutlet var headshotImage: UIImageView! @IBOutlet var artistName: UILabel! func displayContent(artist: Artist) { headshotImage.image = artist.headshot artistName.text = artist.name } }

Slide 66

Slide 66 text

ARTIST.SWIFT import Foundation import UIKit struct Artist { let name: String let headshot: UIImage init(name: String, headshot: UIImage) { self.name = name self.headshot = headshot } } let emmaStone = Artist(name: "Emma Stone", headshot: UIImage("emma.png"))

Slide 67

Slide 67 text

ARTIST.SWIFT import Foundation import UIKit struct Artist { let name: String let headshot: UIImage init(name: String, headshot: UIImage) { self.name = name self.headshot = headshot } } let emmaStone = Artist(name: "Emma Stone", headshot: UIImage("emma.png"))

Slide 68

Slide 68 text

ARTIST.SWIFT import Foundation import UIKit struct Artist { let name: String let headshot: UIImage init(name: String, headshot: UIImage) { self.name = name self.headshot = headshot } } let emmaStone = Artist(name: "Emma Stone", headshot: UIImage("emma.png"))

Slide 69

Slide 69 text

ARTIST.SWIFT import Foundation import UIKit struct Artist { let name: String let headshot: UIImage init(name: String, headshot: UIImage) { self.name = name self.headshot = headshot } } let emmaStone = Artist(name: "Emma Stone", headshot: UIImage("emma.png"))

Slide 70

Slide 70 text

STRUCTS

Slide 71

Slide 71 text

STRUCTS ▸ Best intended for storing data properties

Slide 72

Slide 72 text

STRUCTS ▸ Best intended for storing data properties ▸ ... are value type, meaning it's value is copied when assigned or passed

Slide 73

Slide 73 text

CASTVIEWCONTROLLER import UIKit class CastViewController : UIViewController, UICollectionViewDataSource { @IBOutlet var castCollectionView: UICollectionView! let cast = CastStore.sharedInstance // MARK: UICollectionViewDataSource func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return cast.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "artistCellView", for: indexPath) as! ArtistViewCell let artist = cast[indexPath.row] cell.displayContent(headshot: artist.headshot, name: artist.name) return cell } }

Slide 74

Slide 74 text

CASTVIEWCONTROLLER import UIKit class CastViewController : UIViewController, UICollectionViewDataSource { @IBOutlet var castCollectionView: UICollectionView! let cast = CastStore.sharedInstance // MARK: UICollectionViewDataSource func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return cast.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "artistCellView", for: indexPath) as! ArtistViewCell let artist = cast[indexPath.row] cell.displayContent(headshot: artist.headshot, name: artist.name) return cell } }

Slide 75

Slide 75 text

CASTVIEWCONTROLLER import UIKit class CastViewController : UIViewController, UICollectionViewDataSource { @IBOutlet var castCollectionView: UICollectionView! let cast = CastStore.sharedInstance // MARK: UICollectionViewDataSource func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return cast.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "artistCellView", for: indexPath) as! ArtistViewCell let artist = cast[indexPath.row] cell.displayContent(headshot: artist.headshot, name: artist.name) return cell } }

Slide 76

Slide 76 text

CASTVIEWCONTROLLER import UIKit class CastViewController : UIViewController, UICollectionViewDataSource { @IBOutlet var castCollectionView: UICollectionView! let cast = CastStore.sharedInstance // MARK: UICollectionViewDataSource func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return cast.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "artistCellView", for: indexPath) as! ArtistViewCell let artist = cast[indexPath.row] cell.displayContent(headshot: artist.headshot, name: artist.name) return cell } }

Slide 77

Slide 77 text

CASTVIEWCONTROLLER import UIKit class CastViewController : UIViewController, UICollectionViewDataSource { @IBOutlet var castCollectionView: UICollectionView! let cast = CastStore.sharedInstance // MARK: UICollectionViewDataSource func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return cast.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "artistCellView", for: indexPath) as! ArtistViewCell let artist = cast[indexPath.row] cell.displayContent(headshot: artist.headshot, name: artist.name) return cell } }

Slide 78

Slide 78 text

LET'S DO SOMETHING A BIT MORE COMPLEX

Slide 79

Slide 79 text

SONGS

Slide 80

Slide 80 text

SONGS ▸ UITableView with data source and delegate

Slide 81

Slide 81 text

SONGS ▸ UITableView with data source and delegate ▸ UITableViewCell with 3 UILabel

Slide 82

Slide 82 text

SONGS ▸ UITableView with data source and delegate ▸ UITableViewCell with 3 UILabel ▸ Segue to SongDetailsViewController

Slide 83

Slide 83 text

SONG DETAILS

Slide 84

Slide 84 text

SONG DETAILS ▸ Part of stack of UINavigationController

Slide 85

Slide 85 text

SONG DETAILS ▸ Part of stack of UINavigationController ▸ UIImage

Slide 86

Slide 86 text

SONG DETAILS ▸ Part of stack of UINavigationController ▸ UIImage ▸ UILabel

Slide 87

Slide 87 text

SONG DETAILS ▸ Part of stack of UINavigationController ▸ UIImage ▸ UILabel ▸ UIText

Slide 88

Slide 88 text

SONG DETAILS ▸ Part of stack of UINavigationController ▸ UIImage ▸ UILabel ▸ UIText ▸ UIButton

Slide 89

Slide 89 text

No content

Slide 90

Slide 90 text

No content

Slide 91

Slide 91 text

EXAMPLE OF DATASTORE final class DataStore { static func songs() -> [Song] { var songs = [Song]() songs.append(Song(orderNumber: 1, name: "Another day of sun", artist: "La La Land Cast", isFavorite: true, lyrics: """ I think about that day I left him at a Greyhound station West of Santa Fé """)) return songs } }

Slide 92

Slide 92 text

EXAMPLE OF DATASTORE final class DataStore { static func songs() -> [Song] { var songs = [Song]() songs.append(Song(orderNumber: 1, name: "Another day of sun", artist: "La La Land Cast", isFavorite: true, lyrics: """ I think about that day I left him at a Greyhound station West of Santa Fé """)) return songs } }

Slide 93

Slide 93 text

EXAMPLE OF DATASTORE final class DataStore { static func songs() -> [Song] { var songs = [Song]() songs.append(Song(orderNumber: 1, name: "Another day of sun", artist: "La La Land Cast", isFavorite: true, lyrics: """ I think about that day I left him at a Greyhound station West of Santa Fé """)) return songs } }

Slide 94

Slide 94 text

EXAMPLE OF DATASTORE final class DataStore { static func songs() -> [Song] { var songs = [Song]() songs.append(Song(orderNumber: 1, name: "Another day of sun", artist: "La La Land Cast", isFavorite: true, lyrics: """ I think about that day I left him at a Greyhound station West of Santa Fé """)) return songs } }

Slide 95

Slide 95 text

import UIKit class SongsViewController : UIViewController, UITableViewDataSource, UITableViewDelegate { @IBOutlet var tableView: UITableView! let songs = DataStore.songs() // MARK: UIViewController override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if (segue.identifier == "songDetailsSegue") { if let indexPath = self.tableView.indexPathForSelectedRow { let songDetailsViewController = segue.destination as! SongDetailsViewController let selectedSong = songs[indexPath.row] songDetailsViewController.selectedSong = selectedSong } } } // MARK: UITableViewDataSource func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return songs.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "songCellIdentifier") as! SongViewCell let song = songs[indexPath.row] cell.bind(song: song) return cell } // MARK: UITableViewDelegate func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { performSegue(withIdentifier: "songDetailsSegue", sender: self) } }

Slide 96

Slide 96 text

UITABLEVIEWDATASOURCE import UIKit class SongsViewController : UIViewController, UITableViewDataSource, UITableViewDelegate { let songs = DataStore.songs() ... // MARK: UITableViewDataSource func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return songs.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "songCellIdentifier") as! SongViewCell let song = songs[indexPath.row] cell.bind(song: song) return cell } }

Slide 97

Slide 97 text

UITABLEVIEWDATASOURCE import UIKit class SongsViewController : UIViewController, UITableViewDataSource, UITableViewDelegate { let songs = DataStore.songs() ... // MARK: UITableViewDataSource func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return songs.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "songCellIdentifier") as! SongViewCell let song = songs[indexPath.row] cell.bind(song: song) return cell } }

Slide 98

Slide 98 text

UITABLEVIEWDATASOURCE import UIKit class SongsViewController : UIViewController, UITableViewDataSource, UITableViewDelegate { let songs = DataStore.songs() ... // MARK: UITableViewDataSource func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return songs.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "songCellIdentifier") as! SongViewCell let song = songs[indexPath.row] cell.bind(song: song) return cell } }

Slide 99

Slide 99 text

UITABLEVIEWDATASOURCE import UIKit class SongsViewController : UIViewController, UITableViewDataSource, UITableViewDelegate { let songs = DataStore.songs() ... // MARK: UITableViewDataSource func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return songs.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "songCellIdentifier") as! SongViewCell let song = songs[indexPath.row] cell.bind(song: song) return cell } }

Slide 100

Slide 100 text

UITABLEVIEWDATASOURCE import UIKit class SongsViewController : UIViewController, UITableViewDataSource, UITableViewDelegate { let songs = DataStore.songs() ... // MARK: UITableViewDataSource func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return songs.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "songCellIdentifier") as! SongViewCell let song = songs[indexPath.row] cell.bind(song: song) return cell } }

Slide 101

Slide 101 text

UITABLEVIEWDATASOURCE import UIKit class SongsViewController : UIViewController, UITableViewDataSource, UITableViewDelegate { let songs = DataStore.songs() ... // MARK: UITableViewDataSource func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return songs.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "songCellIdentifier") as! SongViewCell let song = songs[indexPath.row] cell.bind(song: song) return cell } }

Slide 102

Slide 102 text

UITABLEVIEWDELEGATE import UIKit class SongsViewController : UIViewController, UITableViewDataSource, UITableViewDelegate { @IBOutlet var tableView: UITableView! let songs = DataStore.songs() // MARK: UIViewController override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if (segue.identifier == "songDetailsSegue") { if let indexPath = self.tableView.indexPathForSelectedRow { let songDetailsViewController = segue.destination as! SongDetailsViewController let selectedSong = songs[indexPath.row] songDetailsViewController.selectedSong = selectedSong } } } // MARK: UITableViewDelegate func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { performSegue(withIdentifier: "songDetailsSegue", sender: self) } ... }

Slide 103

Slide 103 text

UITABLEVIEWDELEGATE import UIKit class SongsViewController : UIViewController, UITableViewDataSource, UITableViewDelegate { @IBOutlet var tableView: UITableView! let songs = DataStore.songs() // MARK: UIViewController override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if (segue.identifier == "songDetailsSegue") { if let indexPath = self.tableView.indexPathForSelectedRow { let songDetailsViewController = segue.destination as! SongDetailsViewController let selectedSong = songs[indexPath.row] songDetailsViewController.selectedSong = selectedSong } } } // MARK: UITableViewDelegate func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { performSegue(withIdentifier: "songDetailsSegue", sender: self) } ... }

Slide 104

Slide 104 text

UITABLEVIEWDELEGATE import UIKit class SongsViewController : UIViewController, UITableViewDataSource, UITableViewDelegate { @IBOutlet var tableView: UITableView! let songs = DataStore.songs() // MARK: UIViewController override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if (segue.identifier == "songDetailsSegue") { if let indexPath = self.tableView.indexPathForSelectedRow { let songDetailsViewController = segue.destination as! SongDetailsViewController let selectedSong = songs[indexPath.row] songDetailsViewController.selectedSong = selectedSong } } } // MARK: UITableViewDelegate func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { performSegue(withIdentifier: "songDetailsSegue", sender: self) } ... }

Slide 105

Slide 105 text

UITABLEVIEWDELEGATE import UIKit class SongsViewController : UIViewController, UITableViewDataSource, UITableViewDelegate { @IBOutlet var tableView: UITableView! let songs = DataStore.songs() // MARK: UIViewController override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if (segue.identifier == "songDetailsSegue") { if let indexPath = self.tableView.indexPathForSelectedRow { let songDetailsViewController = segue.destination as! SongDetailsViewController let selectedSong = songs[indexPath.row] songDetailsViewController.selectedSong = selectedSong } } } // MARK: UITableViewDelegate func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { performSegue(withIdentifier: "songDetailsSegue", sender: self) } ... }

Slide 106

Slide 106 text

SONGDETAILSVIEWCONTROLLER import UIKit class SongDetailsViewController : UIViewController { var selectedSong: Song! @IBOutlet var artistLabel: UILabel! @IBOutlet var descriptionText: UITextView! @IBOutlet weak var favoriteButton: UIButton! override func viewDidLoad() { super.viewDidLoad() artistLabel.text = selectedSong.artist descriptionText.text = selectedSong.lyrics } @IBAction func toggleFavorite(_ sender: UIButton) { if (selectedSong.isFavorite) { selectedSong.isFavorite = false favoriteButton.setTitle(" ! ", for: UIControlState.normal) } else { selectedSong.isFavorite = true favoriteButton.setTitle(" ❤ ", for: UIControlState.normal) } } }

Slide 107

Slide 107 text

SONGDETAILSVIEWCONTROLLER import UIKit class SongDetailsViewController : UIViewController { var selectedSong: Song! @IBOutlet var artistLabel: UILabel! @IBOutlet var descriptionText: UITextView! @IBOutlet weak var favoriteButton: UIButton! override func viewDidLoad() { super.viewDidLoad() artistLabel.text = selectedSong.artist descriptionText.text = selectedSong.lyrics } @IBAction func toggleFavorite(_ sender: UIButton) { if (selectedSong.isFavorite) { selectedSong.isFavorite = false favoriteButton.setTitle(" ! ", for: UIControlState.normal) } else { selectedSong.isFavorite = true favoriteButton.setTitle(" ❤ ", for: UIControlState.normal) } } }

Slide 108

Slide 108 text

SONGDETAILSVIEWCONTROLLER import UIKit class SongDetailsViewController : UIViewController { var selectedSong: Song! @IBOutlet var artistLabel: UILabel! @IBOutlet var descriptionText: UITextView! @IBOutlet weak var favoriteButton: UIButton! override func viewDidLoad() { super.viewDidLoad() artistLabel.text = selectedSong.artist descriptionText.text = selectedSong.lyrics } @IBAction func toggleFavorite(_ sender: UIButton) { if (selectedSong.isFavorite) { selectedSong.isFavorite = false favoriteButton.setTitle(" ! ", for: UIControlState.normal) } else { selectedSong.isFavorite = true favoriteButton.setTitle(" ❤ ", for: UIControlState.normal) } } }

Slide 109

Slide 109 text

SONGDETAILSVIEWCONTROLLER import UIKit class SongDetailsViewController : UIViewController { var selectedSong: Song! @IBOutlet var artistLabel: UILabel! @IBOutlet var descriptionText: UITextView! @IBOutlet weak var favoriteButton: UIButton! override func viewDidLoad() { super.viewDidLoad() artistLabel.text = selectedSong.artist descriptionText.text = selectedSong.lyrics } @IBAction func toggleFavorite(_ sender: UIButton) { if (selectedSong.isFavorite) { selectedSong.isFavorite = false favoriteButton.setTitle(" ! ", for: UIControlState.normal) } else { selectedSong.isFavorite = true favoriteButton.setTitle(" ❤ ", for: UIControlState.normal) } } }

Slide 110

Slide 110 text

SONGDETAILSVIEWCONTROLLER import UIKit class SongDetailsViewController : UIViewController { var selectedSong: Song! @IBOutlet var artistLabel: UILabel! @IBOutlet var descriptionText: UITextView! @IBOutlet weak var favoriteButton: UIButton! override func viewDidLoad() { super.viewDidLoad() artistLabel.text = selectedSong.artist descriptionText.text = selectedSong.lyrics } @IBAction func toggleFavorite(_ sender: UIButton) { if (selectedSong.isFavorite) { selectedSong.isFavorite = false favoriteButton.setTitle(" ! ", for: UIControlState.normal) } else { selectedSong.isFavorite = true favoriteButton.setTitle(" ❤ ", for: UIControlState.normal) } } }

Slide 111

Slide 111 text

PROFIT TIME! !"

Slide 112

Slide 112 text

... well, not really, right?

Slide 113

Slide 113 text

!

Slide 114

Slide 114 text

Let's talk about networking and persistence

Slide 115

Slide 115 text

NETWORKING

Slide 116

Slide 116 text

NETWORKING ▸ URLRequest as the simplest method. Built in.

Slide 117

Slide 117 text

NETWORKING ▸ URLRequest as the simplest method. Built in. ▸ URLSession seems to be the current preferred option. Built in.

Slide 118

Slide 118 text

NETWORKING ▸ URLRequest as the simplest method. Built in. ▸ URLSession seems to be the current preferred option. Built in. ▸ Alamofire is an easy to use, but heavy, framework

Slide 119

Slide 119 text

NETWORKING ▸ URLRequest as the simplest method. Built in. ▸ URLSession seems to be the current preferred option. Built in. ▸ Alamofire is an easy to use, but heavy, framework ▸ ... but hey, nothing like Retrofit

Slide 120

Slide 120 text

NETWORKING ▸ URLRequest as the simplest method. Built in. ▸ URLSession seems to be the current preferred option. Built in. ▸ Alamofire is an easy to use, but heavy, framework ▸ ... but hey, nothing like Retrofit ▸ ... and let's not even talk about Moshi or Gson...

Slide 121

Slide 121 text

CALLING A REST API let url = URL(string: "https://la.la.land/songs/top?api_key=DEMO_KEY") let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in if let data = data { do { let jsonSerialized = try JSONSerialization.jsonObject(with: data, options: []) as? [String : Any] if let json = jsonSerialized, let name = json["name"], let artist = json["artist"] { print(url) print(explanation) } } catch let error as NSError { print(error.localizedDescription) } } else if let error = error { print(error.localizedDescription) } } task.resume()

Slide 122

Slide 122 text

CALLING A REST API let url = URL(string: "https://la.la.land/songs/top?api_key=DEMO_KEY") let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in if let data = data { do { let jsonSerialized = try JSONSerialization.jsonObject(with: data, options: []) as? [String : Any] if let json = jsonSerialized, let name = json["name"], let artist = json["artist"] { print(url) print(explanation) } } catch let error as NSError { print(error.localizedDescription) } } else if let error = error { print(error.localizedDescription) } } task.resume()

Slide 123

Slide 123 text

CALLING A REST API let url = URL(string: "https://la.la.land/songs/top?api_key=DEMO_KEY") let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in if let data = data { do { let jsonSerialized = try JSONSerialization.jsonObject(with: data, options: []) as? [String : Any] if let json = jsonSerialized, let name = json["name"], let artist = json["artist"] { print(url) print(explanation) } } catch let error as NSError { print(error.localizedDescription) } } else if let error = error { print(error.localizedDescription) } } task.resume()

Slide 124

Slide 124 text

CALLING A REST API let url = URL(string: "https://la.la.land/songs/top?api_key=DEMO_KEY") let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in if let data = data { do { let jsonSerialized = try JSONSerialization.jsonObject(with: data, options: []) as? [String : Any] if let json = jsonSerialized, let name = json["name"], let artist = json["artist"] { print(url) print(explanation) } } catch let error as NSError { print(error.localizedDescription) } } else if let error = error { print(error.localizedDescription) } } task.resume()

Slide 125

Slide 125 text

PERSISTENCE

Slide 126

Slide 126 text

PERSISTENCE ▸ SQLite supported out of the box

Slide 127

Slide 127 text

PERSISTENCE ▸ SQLite supported out of the box ▸ FMDB or SQLite.swift as wrappers

Slide 128

Slide 128 text

PERSISTENCE ▸ SQLite supported out of the box ▸ FMDB or SQLite.swift as wrappers ▸ ... but not as good as Room

Slide 129

Slide 129 text

PERSISTENCE ▸ SQLite supported out of the box ▸ FMDB or SQLite.swift as wrappers ▸ ... but not as good as Room ▸ Oh yeah! And Core Data

Slide 130

Slide 130 text

CORE DATA

Slide 131

Slide 131 text

CORE DATA ▸ Manage object graphs and object lifecycle, including persistence.

Slide 132

Slide 132 text

CORE DATA ▸ Manage object graphs and object lifecycle, including persistence. ▸ Use Core Data to manage the model layer objects in your application.

Slide 133

Slide 133 text

CORE DATA ▸ Manage object graphs and object lifecycle, including persistence. ▸ Use Core Data to manage the model layer objects in your application. ▸ ... so it's like a model and persistence layer, all in one

Slide 134

Slide 134 text

!"

Slide 135

Slide 135 text

No content

Slide 136

Slide 136 text

Biggest efforts on Multiplatform happening on the networking and persistence layers

Slide 137

Slide 137 text

LEARNING RESOURCES

Slide 138

Slide 138 text

LEARNING RESOURCES ▸ Ray Wenderlich, community supported

Slide 139

Slide 139 text

LEARNING RESOURCES ▸ Ray Wenderlich, community supported ▸ Big Nerd Ranch, world class training!

Slide 140

Slide 140 text

LEARNING RESOURCES ▸ Ray Wenderlich, community supported ▸ Big Nerd Ranch, world class training! ▸ NSHipster by @mattt

Slide 141

Slide 141 text

LEARNING RESOURCES ▸ Ray Wenderlich, community supported ▸ Big Nerd Ranch, world class training! ▸ NSHipster by @mattt ▸ Flight School, also by @mattt

Slide 142

Slide 142 text

...and finally...

Slide 143

Slide 143 text

No content

Slide 144

Slide 144 text

Thank you!