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