I am new to swift and iOS programming, but I have been able to build a mostly stable starting interface for my application in Xcode. The CollectionView grabs an image and text from an array of dictionaries created from a csv file on my home network server. The cvs file contains data in the following format (note, urls have been changed to protect licensed images):
csv file
csv file is @ url https://myserver.com/csv.txt and contains the following
Title";"SeriesImageURL
Title1";"https://licensedimage.com/url1
Title2";"https://licensedimage.com/url2
...
Title1000";"https://licensedimage.com/url1000
The problem is that when scrolling quickly through the CollectionView, the cell will grab the incorrect image. Noticeably, if you do a slow or medium scroll, a different image will show before the correct image is rendered into the correct cell (the label text for the cells are always correct, only the image is ever off). After the mismatch of images to proper cell with label occurs, all other cells in the CollectionView will also have incorrect images displayed.
E.g. Cell 1-9 will show Title1-9 with correct Image1-9 When scrolling slowly, Cells 19-27 will show Title 19-27, will briefly show Image 10-18 and then show the correct Image 19-27. When scrolling quickly a huge number of cells (e.g. from cell 1-9 to cell 90-99), Cells 90-99 will show Title 90-99, will show Image 10-50ish, and then will incorrectly stay on Image 41-50(or thereabout). When scrolling further, Cells 100+ will display the correct Title but will only show images from the range Image 41-50.
I think this error is either because the cell reuse isn't handled properly, the caching of images isn't handled properly, or both. It could also be something I am not seeing as a beginner iOS/swift programmer. I have tried to implement a request with a completion modifier but cannot seem to get it working properly with the way my code is set up. I would appreciate any help with this as well as an explanation for why the fix works the way it does. Thanks!
The relevant code is below.
SeriesCollectionViewController.swift
class SeriesCollectionViewController: UICollectionViewController, UISearchBarDelegate {
let reuseIdentifier:String = "SeriesCell"
// Set Data Source Models & Variables
struct seriesModel {
let title: AnyObject
let seriesimageurl: AnyObject
}
var seriesDict = [String:AnyObject]()
var seriesArray = [seriesModel]()
// Image Cache
var imageCache = NSCache()
override func viewDidLoad() {
super.viewDidLoad()
// Grab Data from Source
do {
let url = NSURL(string: "https://myserver.com/csv.txt")
let fullText = try NSString(contentsOfURL: url!, encoding: NSUTF8StringEncoding)
let readings = fullText.componentsSeparatedByString("\n") as [String]
var seriesDictCount = readings.count
seriesDictCount -= 1
for i in 1..<seriesDictCount {
let seriesData = readings[i].componentsSeparatedByString("\";\"")
seriesDict["Title"] = "\(seriesData[0])"
seriesDict["SeriesImageURL"] = "\(seriesData[1])"
seriesArray.append(seriesModel(
title: seriesDict["Title"]!,
seriesimageurl: seriesDict["SeriesImageURL"]!,
))
}
} catch let error as NSError {
print("Error: \(error)")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
imageCache.removeAllObjects()
// Dispose of any resources that can be recreated.
}
//...
//...skipping over some stuff that isn't relevant
//...
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> SeriesCollectionViewCell {
let cell: SeriesCollectionViewCell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! SeriesCollectionViewCell
if (self.searchBarActive) {
let series = seriesArrayForSearchResult[indexPath.row]
do {
// set image
if let imageURL = NSURL(string: "\(series.seriesimageurl)") {
if let image = imageCache.objectForKey(imageURL) as? UIImage {
cell.seriesImage.image = image
} else {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), {
if let tvimageData = NSData(contentsOfURL: imageURL) {
let image = UIImage(data: tvimageData)
self.imageCache.setObject(image!, forKey: imageURL)
dispatch_async(dispatch_get_main_queue(), { () -> Void in
cell.seriesImage.image = nil
cell.seriesImage.image = image
})
}
})
}
}
cell.seriesLabel.text = "\(series.title)"
}
} else {
let series = seriesArray[indexPath.row]
do {
// set image
if let imageURL = NSURL(string: "\(series.seriesimageurl)") {
if let image = imageCache.objectForKey(imageURL) as? UIImage {
cell.seriesImage.image = image
} else {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), {
if let tvimageData = NSData(contentsOfURL: imageURL) {
let image = UIImage(data: tvimageData)
self.imageCache.setObject(image!, forKey: imageURL)
dispatch_async(dispatch_get_main_queue(), { () -> Void in
cell.seriesImage.image = nil
cell.seriesImage.image = image
})
}
})
}
}
cell.seriesLabel.text = "\(series.title)"
}
}
cell.layer.shouldRasterize = true
cell.layer.rasterizationScale = UIScreen.mainScreen().scale
cell.prepareForReuse()
return cell
}
SeriesCollectionViewCell
class SeriesCollectionViewCell: UICollectionViewCell {
@IBOutlet weak var seriesImage: UIImageView!
@IBOutlet weak var seriesLabel: UILabel!
}