Problèmes UITableview, SDWebImage, réutilisation de cellules et UIImage
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
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
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