Solution for Ch 23 Silver Challenge: Favorites in Xcode 9.3

Note that this solution is based on solution for Ch 20 Silver Challenge: Fetch Recent Photos from Flickr.

Photorama.xcdatamodeld - Add a new attribute with name “isFavorite” of type “Boolean” to the Photo entity.

//  TagDataSource.swift

class TagDataSource: NSObject, UITableViewDataSource {
  ...
  func numberOfSections(in tableView: UITableView) -> Int {
    return 2
  }

  func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    return section == 1 ? " " : nil
  }

  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    if section == 1 {
      return 1      // The Favorite "Tag"
    }

    return tags.count
  }

  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "UITableViewCell", for: indexPath)

    if indexPath.section == 1 {
      cell.textLabel?.text = "Favorite"
      cell.textLabel?.font = UIFont.boldSystemFont(ofSize: 17)
      return cell
    }
    
    let tag = tags[indexPath.row]
    cell.textLabel?.text = tag.name
    return cell
  }
}
//  TagsViewController.swift

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  if indexPath.section == 1 {
    photo.isFavorite = !photo.isFavorite
    try? store.persistentContainer.viewContext.save()
    tableView.reloadRows(at: [indexPath], with: .automatic)
    return
  }
  ...
}

override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
  if indexPath.section == 1 {
    cell.accessoryType = photo.isFavorite ? .checkmark : .none
    return
  }
  ...
}
//  PhotoStore.swift

class PhotoStore {
  ...
  func fetchFavoritePhotos(completion: @escaping (PhotosResult) -> Void) {
    let fetchRequest: NSFetchRequest<Photo> = Photo.fetchRequest()
    let sortByDateTaken = NSSortDescriptor(key: #keyPath(Photo.dateTaken), ascending: false)
    fetchRequest.sortDescriptors = [sortByDateTaken]

    let predicate = NSPredicate(format: "(%K == TRUE)", #keyPath(Photo.isFavorite))
    fetchRequest.predicate = predicate

    let viewContext = persistentContainer.viewContext
    viewContext.perform {
      do {
        let favoritePhotos = try viewContext.fetch(fetchRequest)
        completion(.success(favoritePhotos))
      } catch {
        completion(.failure(error))
      }
    }
  }
}
//  PhotosViewController.swift

class PhotosViewController: UIViewController {
  ...
  override func viewDidLoad() {
    super.viewDidLoad()

    let segmentedControl = UISegmentedControl(items: ["Interesting", "Recent", "Favorite"])
    segmentedControl.addTarget(self, action: #selector(selectMethod(_:)), for: .valueChanged)
    navigationItem.titleView = segmentedControl

    collectionView.dataSource = photoDataSource
    collectionView.delegate = self
  }

  @objc func selectMethod(_ sender: UISegmentedControl) {
    sender.isEnabled = false
    let method: Method
    switch sender.selectedSegmentIndex {
    case 0:
      method = .interestingPhotos
    case 1:
      method = .recentPhotos
    default:
      updateDataSourceForFavoritePhotos()
      DispatchQueue.main.async {
        sender.isEnabled = true
      }
      return
    }

    updateDataSource()
    
    store.fetchPhotos(for: method) { (photosResult) -> Void in
      DispatchQueue.main.async {
        sender.isEnabled = true
      }
      self.updateDataSource()
    }
  }

  private func updateDataSourceForFavoritePhotos() {
    store.fetchFavoritePhotos { (photosResult) in
      switch photosResult {
      case let .success(photos):
        self.photoDataSource.photos = photos
      case .failure:
        self.photoDataSource.photos.removeAll()
      }
      self.collectionView.reloadSections(IndexSet(integer: 0))
    }
  }

  private func updateDataSource() {
    store.fetchAllPhotos { (photosResult) in
      switch photosResult {
      case let .success(photos):
        self.photoDataSource.photos = photos
      case .failure:
        self.photoDataSource.photos.removeAll()
      }
      self.collectionView.reloadSections(IndexSet(integer: 0))
    }
  }
}