0

I have a UICollectionView which loads images from an api for users to rate, which works perfectly well. My problem is I want a way to show the user the selected the cell with an emoji based on their rating type (Good, bad etc). Clicking on a cell shows a modal which the user will rate, I have been able to pass data from the modal to the main collection view. Now I want to be able to use the data from the modal to display an emoji on the cell to allow the user to see what they chose

//my protocol delegate for the modal
    protocol ModalDelegate {
    func changeValue(userChoice: String, rateMovieID: String, rateImageUrl: String)
}

    //Main class conforming to modalDelegate

class GuestRateMovieView: UIViewController, ModalDelegate {
    func changeValue(userChoice: String, rateMovieID: String, rateImageUrl: String) {
        self.userChoice = userChoice
        print("choice =>", userChoice, "id =>", rateMovieID, "url =>", rateImageUrl)
    }
}

    //My custom cell

class MoviesCollectionCell: UICollectionViewCell {
    private var movieImages = NSCache<NSString, NSData>()
    weak var textLabel: UILabel!
    let movieImage: UIImageView = {
        let image = UIImageView()
        image.translatesAutoresizingMaskIntoConstraints = false
        image.clipsToBounds = true
        image.contentMode = .scaleAspectFill
        image.layer.cornerRadius = 10
        return image
    }()
    
    let btnRate: UIImageView = {
        let image = UIImageView()
        image.translatesAutoresizingMaskIntoConstraints = false
        
        image.clipsToBounds = true
        image.contentMode = .scaleAspectFit
        image.alpha = 0
        return image
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        contentView.addSubview(movieImage)
        movieImage.addSubview(btnRate)
    }
    
    override func prepareForReuse() {
        super.prepareForReuse()
        movieImage.image = nil
    }
    
    func configure(with urlString: String){
        //check for cached image
        if let imageData = movieImages.object(forKey: urlString as NSString){
//            print("using cache image")
            DispatchQueue.main.async {
                let image = UIImage(data: imageData as Data)
                self.movieImage.image = image
            }
        }else{
            guard let url = URL(string: urlString) else{
                return
            }
            //Downloading images
            URLSession.shared.dataTask(with: url) {[weak self] data, _, error in
                guard let data = data, error == nil else {
                    return
                }
                DispatchQueue.main.async{
                    self?.movieImages.setObject(data as NSData, forKey: url.absoluteString as NSString)
                    let image = UIImage(data: data)
                    self?.movieImage.image = image
                }
            }.resume()
        }
        
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        NSLayoutConstraint.activate([
            movieImage.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10),
            movieImage.topAnchor.constraint(equalTo: contentView.topAnchor),
            movieImage.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10),
            movieImage.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
            
            btnRate.centerXAnchor.constraint(equalTo: movieImage.centerXAnchor),
            btnRate.centerYAnchor.constraint(equalTo: movieImage.centerYAnchor),
            btnRate.widthAnchor.constraint(equalToConstant: 30),
            btnRate.heightAnchor.constraint(equalToConstant: 30)
        ])
    }
    
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reUseMoviesCellID, for: indexPath) as! MoviesCollectionCell
        var movieTitle = [String]()
        for obj in moviesArray {
            if let movieObj = obj as? NSDictionary {
                   let packShotObj = movieObj.value(forKey: "packShot") as! NSDictionary?
                   let id = movieObj.value(forKey: "id") as! String?
                   let title = movieObj.value(forKey: "title") as! String?
                   if let packShots = packShotObj{
                        let thumbs = packShots.value(forKey: "thumbnail") as! String?
                        if let thumbnails = thumbs{
                            movieThumbnails.append(thumbnails)
                            movieTitle.append(title ?? "")
                        }
                    }
            }
        }
        let imageUrl = movieThumbnails[indexPath.row]
        cell.movieImage.image = nil
        cell.configure(with: imageUrl)
        if userChoice == "Hate it" {
            cell.btnRate.image = UIImage(named: "HateIt")
            moviesCollectionView.reloadData()
        }
        
        return cell
    }

This is how the final project should look like

A short demonstration on what I want to achieve. https://www.seasfarm.com/collections.mp4

chevi99
  • 123
  • 1
  • 2
  • 17
  • 2
    Before looking at your actual problem, I can see a few issues here. You should not have a `for` loop in `cellForItemAt`. That function will be executed each time a cell needs to be displayed. You should perform that work once, when your data is loaded. Second, your cache won't really work how you want it to, since it is an instance property. It needs to be a static property so that it is shared across all cell instances. You should probably look at using something like SDWebImage or Kingfisher. – Paulw11 Dec 29 '22 at 22:57
  • 1
    You don't show how you create your movie objects, but assuming they are coming from some sort of JSON source, you should use a `Codable` struct so that you can access properties directly instead of using key-value access – Paulw11 Dec 29 '22 at 22:58
  • 2
    You must not call `reloadData` from inside the `cellForItemAt` function. That will lead to an endless loop of reloading and cell fetching. Very bad. – HangarRash Dec 29 '22 at 23:13
  • 1
    You need to add an action handler to your button and in that action handler update your data model and, ultimately, call `reloadItems(at:)` to reload the cell (You should not reload the whole collection view when only one cell changes; it is a bad visual appearance and affects the scroll point). There are a couple of approaches you can use - either a delegation pattern or a closure, to get this button event back to the view controller from the cell - https://stackoverflow.com/questions/28659845/how-to-get-the-indexpath-row-when-an-element-is-activated/ – Paulw11 Dec 29 '22 at 23:22
  • 1
    @Paulw11 Since the OP stated *"I have been able to pass data from the modal to the main collection view"* then it's a trivial matter of updating the data modal and reloading that one cell. The current `cellForItemAt` may need to be updated to pass the rating to the cell (along with all of the other changes mentioned). – HangarRash Dec 29 '22 at 23:31
  • @HangarRash I assumed that that might be a typo and they meant to say "I have been unable..." since the code does not any update to the data model, nor any display of the modal dialog, but I could be wrong. In which case, additional code is simply required in `changeValue` and in `cellForItemAt` – Paulw11 Dec 30 '22 at 00:04
  • Also I note that the misplaced `for` loop is updating separate arrays for title and thumbnails. This is also a code smell. You should have a single array with objects that hold these properties. – Paulw11 Dec 30 '22 at 00:08
  • @Paulw11 well noted I will use SDWebImage for my image rendering and also proper caching. – chevi99 Dec 30 '22 at 05:20
  • @Paulw11 that wasn't a typo. I have been able to send back whatever the user chooses on the modal back to the collectionView view controller. The problem is how to update the cell with the feedback. Eg: If modal comes back with "Hate it" rating I will update the cell with a thumbs down icon. I have uploaded a short video on what I want to achieve. – chevi99 Dec 30 '22 at 05:40
  • You should have an array of objects that is your data model. Update the `hateIt` property of the object at the relevant index path and then reload that indexPath. – Paulw11 Dec 31 '22 at 12:42
  • @Paulw11 can you assist me with a sample code snippet on your implementation. And Oh thanks man I have implemented SDWebImage and that's what I'm using to load my images and caching them – chevi99 Jan 01 '23 at 18:03

0 Answers0