Search code examples
swiftuitableviewuserdefaults

Add to Favorite using Custom TableViewCell via UIButton & Save to UserDefaults


Goal: Create an "add to favorites" button in a Custom TableViewCell which then saves the list of favorites into UserDefaults such that we can get a tableview of favorites only. (I'm using segmentedControl as the means to filter where libraryFilter = 0(Plan) / 1 = Tag / 2 = Duration / 3 = Favorite)

jsonErgWorkouts is the array of workouts from a JSON file which is the basis of the filtering.

This is my what I have coded up.

Custom TableViewCell:

import UIKit

class ErgWorkoutCell: UITableViewCell {
  
  @IBOutlet weak var ergWorkoutTitle: UILabel! 
}

In the TableView View Controller

  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let selectedGroup = selectedWorkoutGroup(libraryFilter: libraryFilter, jsonErgWorkouts: jsonErgWorkouts, workoutGroupBox: workoutGroupBox)
    
    /// this is cast AS! ErgWorkoutCell since this is a custom tableViewCell
    let cell = tableView.dequeueReusableCell(withIdentifier: "ergWorkoutCell") as! ErgWorkoutCell
    
    cell.ergWorkoutTitle.text = selectedGroup[indexPath.row].title
    return cell
  }
}


func selectedWorkoutGroup(libraryFilter: Int, jsonErgWorkouts:[workoutList], workoutGroupBox: UITextField) -> [workoutList] {
  var selectedGroup = [workoutList]()
    
  if libraryFilter == 0 { // Segmented Control #1 (Plan)
    selectedGroup = jsonErgWorkouts.filter { $0.group == workoutGroupBox.text }
  /// code for libraryFilter 1(Tag)
  ///      and libraryFilter 2(Duration) 
  } else if libraryFilter == 3 {
    // Need code here to return the filtered JSON of favorites only
  }
  
  return selectedGroup
}

enter image description here


Solution

  • Few things needed to be done. I'll also link some of the SO pages which contributed to get to this solution.

    1. Add a button into storyboard
    2. Add a Button definition (IBOutlet/IBAction) & a closure into Custom TableViewCell
    3. Add a way to Store the selected Favorite into UserDefaults
    4. Show the list of Favorites in the segmentedControl.

    Let's get started:

    1. Add a button into Storyboard

      I'll leave this to the user

    2. Add a Button definition (IBOutlet/IBAction) & a closure into Custom TableViewCell

      import UIKit
      class ErgWorkoutCell: UITableViewCell {
      
        @IBOutlet weak var ergWorkoutTitle: UILabel!
      
        /// Create and Connect your Button outlet
        @IBOutlet weak var ergWorkoutFavBtn: UIButton!
      
        /// create your closure here
        /// https://stackoverflow.com/a/55743790/14414215
        var favButtonPressed : (() -> ()) = {}
      
        /// Create & Connect an IBAction when your button is pressed. 
        @IBAction func ergWorkoutFavBtn(_ sender: UIButton) {
          /// Call your closure here
          favButtonPressed()
        }
      }
      
    3. Add a way to Store the selected Favorite into UserDefaults

      func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
      
      /// https://stackoverflow.com/a/37357869/14414215
      /// https://stackoverflow.com/q/65355061/14414215
      /// Provide an initial default (empty array) to the UserDefaults Array since on startup there is no fav in the list.
      var rowsWhichAreChecked = UserDefaults.standard.array(forKey: "workoutFavorite") as? [Int] ?? [Int]()
      
      /// this is cast AS! ErgWorkoutCell since this is a custom tableViewCell
      let cell = tableView.dequeueReusableCell(withIdentifier: "ergWorkoutCell") as! ErgWorkoutCell
      
      cell.ergWorkoutTitle.text = selectedGroup[indexPath.row].title
      
      /// This is to change the image between favourited or Not
      if rowsWhichAreChecked.contains(selectedGroup[indexPath.row].id) {
        cell.ergWorkoutFavBtn.setImage(#imageLiteral(resourceName: "star-full"), for: .normal)
      } else {
        cell.ergWorkoutFavBtn.setImage(#imageLiteral(resourceName: "star-empty"), for: .normal)
      }
      
      /// https://stackoverflow.com/a/55743790/14414215
      /// Once the FavButton is pressed, we determine if the selected item is already 
      /// in the FavList. If Yes, we remove it, if Not, we add it in.
      /// We then reload the table
      /// Added [ weak self] in based on @azehe
      /// https://stackoverflow.com/q/64297651/14414215
      cell.favButtonPressed = { [ weak self ] in
        if rowsWhichAreChecked.contains(selectedGroup[indexPath.row].id) {
          let removeIdx = rowsWhichAreChecked.lastIndex(where: {$0 == selectedGroup[indexPath.row].id})
          rowsWhichAreChecked.remove(at: removeIdx!)
      
          cell.ergWorkoutFavBtn.setImage(#imageLiteral(resourceName: "star-empty"), for: .normal)
        } else {
          cell.ergWorkoutFavBtn.setImage(#imageLiteral(resourceName: "star-full"), for: .normal)
          rowsWhichAreChecked.append(selectedGroup[indexPath.row].id)
        }
        UserDefaults.standard.set(rowsWhichAreChecked, forKey: "workoutFavorite")
        self?.vcLibTableView.reloadData() // self? instead of self (with [weak self] in
      }
        return cell
      }
      
    4. Show the list of Favorites in the segmentedControl.

       func selectedWorkoutGroup(libraryFilter: Int, jsonErgWorkouts:[workoutList], workoutGroupBox: UITextField) -> [workoutList] {
         var selectedGroup = [workoutList]()
         
         /// https://stackoverflow.com/a/37357869/14414215
         let workoutFav = UserDefaults.standard.array(forKey: "workoutFavorite") as? [Int] ?? [Int]()
    
         if libraryFilter == 0 {
           selectedGroup = jsonErgWorkouts.filter { $0.group == workoutGroupBox.text }
         } else if libraryFilter == 1 {
           /// https://stackoverflow.com/q/65355061/14414215
           selectedGroup = workoutFav.flatMap { favouriteId in // for each favourite ID
                 jsonErgWorkouts.filter { $0.id == favouriteId }
          } // flatMap joins all those arrays returns by "filter" together, no need to do anything else
         }
         
         return selectedGroup
       }
    

    Result

    Adding [weak self] in based on input from @azehe comment. You can see the difference in the Memory Graph. Debug Memory Graph differences