Problèmes UITableview, SDWebImage, réutilisation de cellules et UIImage

Sep 13 2020

Voir l'exemple vidéo ici: https://imgur.com/a/vSBPjlP

Avoir une tableview avec des images que je télécharge de manière asynchrone à partir d'Internet. Je suis capable de les faire remplir correctement et sans problème avec mon propre code ou SDWebImage. Mon problème commence lorsque je pagine plus de données à la fin de la tableview. La pagination fonctionne. Comme toutes les données de texte, tout fonctionne et reste correctement tout au long de la vue du tableau. Le problème que j'ai est avec mon ImageView. Comme vous pouvez le voir dans la vidéo, après la pagination, l' UIImageViewsimage change toutes les cellules de l'appel précédent en ce qui semble être l'image de ce qui se trouve dans la cellule la plus récente à la fin de la vue du tableau. Qu'est-ce qui motive ce comportement et pourquoi cela n'arrive-t-il qu'à mes vues d'image et que puis-je faire pour y remédier?

ive a essayé de définir l'image imageview nulle prepareForReuse(contre les recommandations d'Apple) et cela produit le même résultat.

Code pertinent

Appel API

 func downloadGamesByPlatformIDJSON(platformID: Int?, fields: String?, include: String?, pageURL: String?, completed: @escaping () -> () ) {
        var urlString : String?
        
        if pageURL == nil {
            urlString = "https://api.thegamesdb.net/v1/Games/ByPlatformID?apikey=\(apiKey)&id=\(platformID!)"
            
            if fields != nil {
                urlString = urlString! + "&fields=" + fields!
            }
            if include != nil {
                
                urlString = urlString! + "&include=" + include!
            }
            
        } else {
            urlString = pageURL!
        }
        let url = URL(string: "\(urlString!)")!
        var requestHeader = URLRequest.init(url: url)
        requestHeader.httpMethod = "GET"
        requestHeader.setValue("application/json", forHTTPHeaderField: "Accept")
        
        URLSession.shared.dataTask(with: requestHeader) { (data, response, error) in
            
            if error != nil {
                print("error = \(error)")
                completed()
            }
            
            if error == nil {
                do {
                    print("error = nil")
                    let json = String(data: data!, encoding: .utf8)
                    
                    print(json)
                    
                    if let jsonDecodedPlatforms = try JSONDecoder().decode(ByPlatformIDData?.self, from: data!) {
                        let decodedJSON = jsonDecodedPlatforms.data?.games
                        self.boxart = jsonDecodedPlatforms.include.boxart
                        self.baseURL = jsonDecodedPlatforms.include.boxart.baseURL
                        self.page = jsonDecodedPlatforms.pages
                        
     
                        self.games.append(contentsOf: decodedJSON!                
                            }
                    
                    DispatchQueue.main.async {
                        completed()
                    }
                } catch {
                    print(error)
                }
                
            }
            
        }.resume()
        
    }

Pagination

func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let offsetY = scrollView.contentOffset.y
        let contentHeight = scrollView.contentSize.height
        
        if offsetY > contentHeight - scrollView.frame.size.height {
            
            if !fetchingMore {
                beginBatchFetch() 
            }
        }
    }
    func beginBatchFetch() {
        fetchingMore = true
        print("fetching data")
        network.downloadGamesByPlatformIDJSON(platformID: nil, fields: nil, include: nil, pageURL: network.page?.next) {
            print("pagination successful")
            self.fetchingMore = false
            self.tableView.reloadData()
                      
                  }
        
    }

cellForRowAt

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

        //creating game object
        let game1 : GDBGamesPlatform?
        game1 = network.games[indexPath.row]

 //retrieving the filename information based on the game id
        
        if network.boxart?.data["\(game1!.id!)"]?[0].side == .front {
            print(network.boxart?.data["\(game1!.id!)"]?[0].filename)
            frontImageName = network.boxart?.data["\(game1!.id!)"]?[0].filename as! String
            
        } else if network.boxart?.data["\(game1!.id!)"]?[0].side == .back {
            backImageName = network.boxart?.data["\(game1!.id!)"]?[0].filename as! String
            
        }
        
        if network.boxart?.data["\(game1!.id!)"]?.count == 2 {
        if network.boxart?.data["\(game1!.id!)"]?[1].side == .front {
            frontImageName = network.boxart?.data["\(game1!.id!)"]?[1].filename as! String
                       
        } else if network.boxart?.data["\(game1!.id!)"]?[1].side == .back {
            backImageName = network.boxart?.data["\(game1!.id!)"]?[1].filename as! String
                       
        }
        }
   
        
        
        //creating image url string
        var imageUrlString = network.baseURL!.small + frontImageName
        print(imageUrlString)
        let imageURL = URL(string: imageUrlString)
        
        
        
        
        //if data exists for the front cover image download it, otherwise show default image
        if frontImageName != nil {
            
            cell.loadCoverImageWith(urlString: imageUrlString)
            
        } else {
            cell.tableViewCoverImage.image = UIImage(named: "noArtNES")

        }
        
        return cell
        
    }

import UIKit
import SDWebImage

class ViewControllerTableViewCell: UITableViewCell {
    @IBOutlet weak var tableViewCoverImage: UIImageView!
    @IBOutlet weak var tableViewGameName: UILabel!
    @IBOutlet weak var tableViewGenreLabel: UILabel!
    @IBOutlet weak var tableViewAgeRatingLabel: UILabel!
    @IBOutlet weak var tableViewCompanyLabel: UILabel!
    @IBOutlet weak var tableViewReleaseDateLabel: UILabel!
    @IBOutlet weak var backgroundCell: UIView!
    @IBOutlet weak var tableViewCoverRearImage: UIImageView!
    @IBOutlet weak var gameCartImage: UIImageView!
    
    
    override func prepareForReuse() {
           super.prepareForReuse()
        
        tableViewCoverImage.sd_cancelCurrentImageLoad()
        tableViewCoverImage.image = nil
             }
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
        
                                                  
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }
    
    func loadCoverImageWith(urlString: String) {
        let imageURL = URL(string: urlString)
       
            
            self.tableViewCoverImage.sd_setImage(with: imageURL, placeholderImage: UIImage(named: "noArtNES"), options: SDWebImageOptions.highPriority) { (image, error, cacheType, url) in
                
                if let error = error {
                    print("Error downloading the image.  Error Description: \(error.localizedDescription)")
                } else {
                    let edgeColor = self.tableViewCoverImage.image?.edgeColor()
                    self.tableViewCoverImage.layer.shadowColor = edgeColor!.cgColor
                }
            }

        
        
        
    }
    

    func configureCells() {
        tableViewCoverImage.layer.shadowOffset = CGSize(width: -5, height: 8)
        tableViewCoverImage.layer.shadowRadius = 8
        tableViewCoverImage.layer.shadowOpacity = 0.8
        tableViewCoverImage.layer.cornerRadius = 10
        tableViewCoverImage.clipsToBounds = false
        tableViewCoverImage.layer.masksToBounds = false
        backgroundCell.layer.shadowOffset = CGSize(width: 0, height: 5)
        backgroundCell.layer.shadowRadius = 5
        backgroundCell.layer.shadowOpacity = 0.1
        backgroundCell.layer.cornerRadius = 10
        if self.traitCollection.userInterfaceStyle == .light {
            backgroundCell.layer.shadowColor = UIColor.black.cgColor
            backgroundCell.layer.backgroundColor = UIColor.white.cgColor

        } else {
            backgroundCell.layer.shadowColor = UIColor.white.cgColor
            backgroundCell.layer.backgroundColor = UIColor.black.cgColor

    }
    
   

}
}

Réponses

1 Dale Sep 16 2020 at 04:24

Le deuxième et les derniers appels à l'API ajoutent les résultats des jeux au tableau, mais écrasent les résultats pour boxart. Les résultats des pages suivantes n'incluront pas le carton des pages précédentes.

Ainsi, vos données de boxart n'incluent plus l'art des pages précédentes. La raison pour laquelle vous obtenez l'image du dernier élément, est que les variables frontImageName et backImageName ont la mauvaise portée, elles doivent être locales à tableView (: cellForRowAt :)

Vous devez fusionner les résultats de l'appel d'API avec le dictionnaire boxart existant en appelant Dictionary.merge (_: uniquingKeysWith :). Voirhttps://developer.apple.com/documentation/swift/dictionary/3127171-merge pour plus de détails

AnisMansuri Sep 13 2020 at 15:16

C'est un problème de réutilisabilité. Essayez de modifier cette ligne dans cellForRowAtIndex.

tableview.register(UINib(nibName: "TableViewCell", bundle: nil), forCellReuseIdentifier: "cell\(indexPath.row)")
let cell = tableView.dequeueReusableCell(withIdentifier: "cell\(indexPath.row)", for: indexPath) as! ViewControllerTableViewCell