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

let swift == val kotlin -> Why iOS is so import...

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
  2. LA LA LAND DROIDCON EDITION ▸ Cast ▸ Songs ▸

    Song details ▸ Mark song as favorite
  3. !

  4. ... and, if possible, we do not want to write

    the same business logic twice
  5. WHY NOT? ▸ Particular to our app/use case ▸ Time

    ▸ Money ▸ (Du)plicated efforts
  6. !

  7. MULTIPLATFORM EFFORTS PRINCIPLES Isolate your business logic from: - Presentation

    - Operating system ▸ Also known as layers, clean code, the onion model...
  8. WHAT ARE WE GOING TO ACHIEVE IN THIS TALK? ▸

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

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

    Intro to iOS development ▸ Xcode ▸ Swift ▸ UI design with Storyboards
  11. 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
  12. ANDROID IOS XML Layout Storyboard Activity/Fragment/ CustomView UIViewController BottomNavigationBar TabBarController

    RecyclerView with GridLayoutManager UICollectionView ImageView UIImageView TextView UILabel
  13. 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. } }
  14. 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. } }
  15. 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. } }
  16. 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. } }
  17. 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. } }
  18. 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. } }
  19. UIKIT? ▸ Cocoa Touch is a framework for developing iOS

    applications. ▸ UIKit for UI components (buttons, labels)
  20. 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
  21. WHERE IS MY Adapter AND MY ViewHolder? protocol is the

    equivalent of an interface protocol extensions as Kotlin extensions
  22. 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
  23. 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
  24. 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
  25. 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
  26. 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 } }
  27. 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 } }
  28. @IBOUTLET It is a keyword added to a variable declaration.

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

    It binds your code method to a UI action in the storyboard
  30. 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 } }
  31. 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 } }
  32. 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"))
  33. 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"))
  34. 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"))
  35. 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"))
  36. STRUCTS ▸ Best intended for storing data properties ▸ ...

    are value type, meaning it's value is copied when assigned or passed
  37. 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 } }
  38. 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 } }
  39. 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 } }
  40. 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 } }
  41. 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 } }
  42. SONGS ▸ UITableView with data source and delegate ▸ UITableViewCell

    with 3 UILabel ▸ Segue to SongDetailsViewController
  43. 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 } }
  44. 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 } }
  45. 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 } }
  46. 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 } }
  47. 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) } }
  48. 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 } }
  49. 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 } }
  50. 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 } }
  51. 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 } }
  52. 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 } }
  53. 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 } }
  54. 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) } ... }
  55. 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) } ... }
  56. 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) } ... }
  57. 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) } ... }
  58. 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) } } }
  59. 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) } } }
  60. 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) } } }
  61. 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) } } }
  62. 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) } } }
  63. !

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

    URLSession seems to be the current preferred option. Built in.
  65. 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
  66. 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
  67. 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...
  68. 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()
  69. 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()
  70. 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()
  71. 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()
  72. PERSISTENCE ▸ SQLite supported out of the box ▸ FMDB

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

    or SQLite.swift as wrappers ▸ ... but not as good as Room ▸ Oh yeah! And Core Data
  74. CORE DATA ▸ Manage object graphs and object lifecycle, including

    persistence. ▸ Use Core Data to manage the model layer objects in your application.
  75. 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
  76. !"

  77. LEARNING RESOURCES ▸ Ray Wenderlich, community supported ▸ Big Nerd

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

    Ranch, world class training! ▸ NSHipster by @mattt ▸ Flight School, also by @mattt