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

let swift == val kotlin -> Why iOS is so important for Android, and vice versa

let swift == val kotlin -> Why iOS is so important for Android, and vice versa

Droidcon NYC 2018

Jorge Coca

August 27, 2018
Tweet

More Decks by Jorge Coca

Other Decks in Programming

Transcript

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

    View Slide

  2. LET'S BUILD A NEW startup

    View Slide

  3. la la land
    DROIDCON NYC EDITION

    View Slide

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

    View Slide

  5. View Slide

  6. !

    View Slide

  7. MODERN DEVELOPMENT OPTIONS

    View Slide

  8. MODERN DEVELOPMENT OPTIONS
    ▸ Native

    View Slide

  9. MODERN DEVELOPMENT OPTIONS
    ▸ Native
    ▸ Progressive Web App

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  15. View Slide

  16. WHY NOT?

    View Slide

  17. WHY NOT?
    ▸ Particular to our app/use case

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  21. THAT SOUNDS AWESOME, BUT...

    View Slide

  22. CAN WE DO IT?

    View Slide

  23. !

    View Slide

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

    View Slide

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

    View Slide

  26. View Slide

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

    View Slide

  28. WHAT ARE WE GOING TO ACHIEVE IN THIS TALK?

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  33. 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

    View Slide

  34. LET'S GET
    STARTED!

    View Slide

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

    View Slide

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

    View Slide

  37. View Slide

  38. THERE'S A TON OF
    DRAG & DROP
    IN STORYBOARDS

    View Slide

  39. 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.
    }
    }

    View Slide

  40. 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.
    }
    }

    View Slide

  41. 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.
    }
    }

    View Slide

  42. 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.
    }
    }

    View Slide

  43. 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.
    }
    }

    View Slide

  44. EASY, RIGHT?

    View Slide

  45. 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.
    }
    }

    View Slide

  46. UIKIT?

    View Slide

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

    View Slide

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

    View Slide

  49. 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

    View Slide

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

    View Slide

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

    View Slide

  52. View Slide

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

    View Slide

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

    View Slide

  55. 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

    View Slide

  56. 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

    View Slide

  57. 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

    View Slide

  58. 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

    View Slide

  59. 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
    }
    }

    View Slide

  60. 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
    }
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  64. 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
    }
    }

    View Slide

  65. 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
    }
    }

    View Slide

  66. 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"))

    View Slide

  67. 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"))

    View Slide

  68. 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"))

    View Slide

  69. 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"))

    View Slide

  70. STRUCTS

    View Slide

  71. STRUCTS
    ▸ Best intended for storing data properties

    View Slide

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

    View Slide

  73. 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
    }
    }

    View Slide

  74. 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
    }
    }

    View Slide

  75. 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
    }
    }

    View Slide

  76. 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
    }
    }

    View Slide

  77. 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
    }
    }

    View Slide

  78. LET'S DO SOMETHING A BIT
    MORE COMPLEX

    View Slide

  79. SONGS

    View Slide

  80. SONGS
    ▸ UITableView with data source and delegate

    View Slide

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

    View Slide

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

    View Slide

  83. SONG DETAILS

    View Slide

  84. SONG DETAILS
    ▸ Part of stack of
    UINavigationController

    View Slide

  85. SONG DETAILS
    ▸ Part of stack of
    UINavigationController
    ▸ UIImage

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  89. View Slide

  90. View Slide

  91. 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
    }
    }

    View Slide

  92. 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
    }
    }

    View Slide

  93. 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
    }
    }

    View Slide

  94. 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
    }
    }

    View Slide

  95. 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)
    }
    }

    View Slide

  96. 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
    }
    }

    View Slide

  97. 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
    }
    }

    View Slide

  98. 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
    }
    }

    View Slide

  99. 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
    }
    }

    View Slide

  100. 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
    }
    }

    View Slide

  101. 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
    }
    }

    View Slide

  102. 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)
    }
    ...
    }

    View Slide

  103. 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)
    }
    ...
    }

    View Slide

  104. 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)
    }
    ...
    }

    View Slide

  105. 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)
    }
    ...
    }

    View Slide

  106. 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)
    }
    }
    }

    View Slide

  107. 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)
    }
    }
    }

    View Slide

  108. 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)
    }
    }
    }

    View Slide

  109. 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)
    }
    }
    }

    View Slide

  110. 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)
    }
    }
    }

    View Slide

  111. PROFIT TIME!
    !"

    View Slide

  112. ... well, not really, right?

    View Slide

  113. !

    View Slide

  114. Let's talk about
    networking
    and
    persistence

    View Slide

  115. NETWORKING

    View Slide

  116. NETWORKING
    ▸ URLRequest as the simplest method. Built in.

    View Slide

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

    View Slide

  118. 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

    View Slide

  119. 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

    View Slide

  120. 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...

    View Slide

  121. 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()

    View Slide

  122. 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()

    View Slide

  123. 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()

    View Slide

  124. 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()

    View Slide

  125. PERSISTENCE

    View Slide

  126. PERSISTENCE
    ▸ SQLite supported out of the box

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  130. CORE DATA

    View Slide

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

    View Slide

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

    View Slide

  133. 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

    View Slide

  134. !"

    View Slide

  135. View Slide

  136. Biggest efforts on Multiplatform
    happening on the networking and
    persistence layers

    View Slide

  137. LEARNING RESOURCES

    View Slide

  138. LEARNING RESOURCES
    ▸ Ray Wenderlich, community supported

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  142. ...and finally...

    View Slide

  143. View Slide

  144. Thank you!

    View Slide