See video example here: https://i.sstatic.net/A93hm.jpg
Have a tableview with images im downloading asynchronously from the internet. Im able to get them to populate correctly and without issue with either my own code or SDWebImage. My problem begins when I paginate more data to the end of the tableview. Pagination does work. As does all the text data, it all works and remains correctly throughout the tableview. The problem im having is with my ImageView. As you can see in the video, after pagination, the UIImageViews
image changes all the cells from the previous call to what appears to be the image of whats in the most recent cell at the end of the tableview. What is driving this behavior and why is it only happening to my imageviews and what can I do to fix it?
ive tried setting the imageview image nil in prepareForReuse
(against Apples recommendations) and it produces the same result.
Relevant code
API Call
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
}
}
}
The second and later calls to the API are appending the results for games to the array but overwriting the results for boxart. The results for the later pages will not include the boxart from earlier pages.
So, your boxart data no longer includes the art for earlier pages. The reason you are getting the image from the last item, is that the variables frontImageName and backImageName have the wrong scope, they should be local to tableView(:cellForRowAt:)
You need to merge the results of the API call with existing boxart dictionary by calling Dictionary.merge(_:uniquingKeysWith:). See https://developer.apple.com/documentation/swift/dictionary/3127171-merge for details