简体   繁体   中英

Saving Multiple Objects with userDefaults Array in TableViewCell - Swift 5

I'm implementing a Favorite feature inside a collectionview cell. The user should tap the favorite button in the cell to save the item. However, when the button is tapped to save the data from the custom object that populates the cell, only one object is being saved. If I tap to favorite another item, that one replaces the previous one, instead of being added to an array of favorites. Wondering what I'm missing here..

Here is the code:

Custom Object (Entity.swift)

struct MediaObject: Codable {
  let name: String
  var results: [Entity]
}

struct Entity: Codable {

var id: Int? 
var name: String? 
var kind: String?
var artwork: String? 
var genre: String? 
var artist: String? 

  private enum CodingKeys: String, CodingKey {

           case id = "trackId"
           case name = "trackName"
           case kind = "kind"
           case artwork = "artworkUrl100"
           case genre = "primaryGenreName"
           case artist = "artistName"
       }


  init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)
    id = try values.decodeIfPresent(Int.self, forKey: .id)
    name = try values.decodeIfPresent(String.self, forKey: .name)
    kind = try values.decodeIfPresent(String.self, forKey: .kind)
    artwork = try values.decodeIfPresent(String.self, forKey: .artwork)
    genre = try values.decodeIfPresent(String.self, forKey: .genre)
    artist = try values.decodeIfPresent(String.self, forKey: .artist)

  }

}

View Controller.swift

class MediaViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {


 var obj:MediaObject? = nil {
     didSet {
         collectionView.reloadData()
     }
 }


  @IBOutlet weak var collectionView: UICollectionView!
  @IBOutlet weak var sectionLabel: UILabel!

  override func awakeFromNib() {
    super.awakeFromNib()
    // Initialization code

      if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
      flowLayout.estimatedItemSize = CGSize(width: 100, height: 90)
    }
}


  func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {

    return obj!.results.count   
  }

  func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! MediaCollectionViewCell

    if let obj = obj {
      cell.entity = obj.results[indexPath.row]
      cell.artistLabel.text = obj.results[indexPath.row].artist
      cell.trackLabel.text = obj.results[indexPath.row].name
    }

    return cell
  }



  func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {

    return CGSize(width: 250, height: 205)
  }

}

And here is my MediaCollectionViewCell.swift class:

MediaCollectionViewCell: UICollectionViewCell {


  @IBOutlet weak var artistLabel: UILabel!
  @IBOutlet weak var trackLabel: UILabel!
  @IBOutlet weak var imageView: UIImageView!
  @IBOutlet weak var favButton: UIButton!

  var favoritesArray = [Entity]()

  var entity:Entity? = nil {
    didSet {
      if let entity = entity,
        let artworkUrl = NSURL(string: entity.artwork!) {
        self.imageView.sd_setImage(with: artworkUrl as URL)
      }
    }
  }


  @IBAction func favButtonPressed(_ sender: Any) {

    print("isFavorited")
  let encoder = JSONEncoder()
         if let encoded = try? encoder.encode([entity]) {
                  let defaults = UserDefaults.standard
                  defaults.set(encoded, forKey: "entity")
              }

let defaultsData = UserDefaults.standard.data(forKey: "entity")

    let decoder = JSONDecoder()
    let loadedPerson = try? decoder.decode(Entity.self, from: defaultsData!) 

favoritesArray.append(loadedPerson)
print(loadedPerson)

print(loadedPerson?.count)


}

Try this:

  1. Create a function that retrieves the saved entities
  2. If there are no saved entities, save an Array of Entities , ie [Entity]
  3. Else Append the new entity to the already saved Entities

Issue:

You've created the favoritesArray inside the MediaCollectionViewCell . Everytime a cell is reused, favoritesArray will be reinitialised, ie the entity added in it earlier will be lost.

That's the reason you always find a single entity inside it and that too of that particular cell .

Solution:

Instead of creating an extra favoritesArray inside the cell, try using closure to save the favourite entities. Here is the approach you can follow.

1. First of all use class instead of struct and add a isFavourite property inside the Entity model, ie

class MediaObject: Codable {
    let name: String
    var results: [Entity]
}

class Entity: Codable {
    var id: Int?
    var name: String?
    var kind: String?
    var artwork: String?
    var genre: String?
    var artist: String?
    var isFavourite = false 

    enum CodingKeys: String, CodingKey {
        case id = "trackId"
        case name = "trackName"
        case kind = "kind"
        case artwork = "artworkUrl100"
        case genre = "primaryGenreName"
        case artist = "artistName"
    }
}

Note: There is any need to create init(from:) inside the Entity model. Use it when there is any special handling.

2. Now, inside MediaCollectionViewCell create a closure favouriteHandler and call it inside favButtonPressed(_:) , ie

class MediaCollectionViewCell: UICollectionViewCell {
    //rest of the code...

    var favouriteHandler: (()->())?

    @IBAction func favButtonPressed(_ sender: Any) {
        print("isFavorited")
        self.favouriteHandler?()
    }
}

3. Next, in collectionView(_:cellForItemAt:) set the favouriteHandler to update the isFavourite status of entity and then save all the entities with isFavourite = true in the UserDefaults .

Use filter(_:) to get all the favourite entities.

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! MediaCollectionViewCell
    if let entity = obj?.results[indexPath.row] {
        cell.entity = entity
        cell.artistLabel.text = entity.artist
        cell.trackLabel.text = entity.name

        cell.favouriteHandler = {[weak self] in //here...
            entity.isFavourite = !entity.isFavourite
            let favourites = self?.obj?.results.filter{ $0.isFavourite }
            let data = try? JSONEncoder().encode(favourites)
            UserDefaults.standard.set(data, forKey: "entity")
        }

    }
    return cell
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM