swift iOS – 在快速滚动后混合了UICollectionView图像

我是swift和iOS编程的新手,但是我已经能够在Xcode中为我的应用程序构build一个基本稳定的启动界面。 CollectionView从我的家庭networking服务器上的csv文件创build的字典数组中抓取图像和文本。 cvs文件包含以下格式的数据(请注意,url已被更改以保护许可的图像):

csv文件

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 

问题是,通过CollectionView快速滚动时,单元格会抓取不正确的图像。 值得注意的是,如果您进行慢速或中速滚动,则在将正确的图像渲染到正确的单元格(单元格的标签文本始终正确,只有图像永远closures)之前,会显示不同的图像。 在发生图像不匹配的情况下,CollectionView中的所有其他单元格也将显示不正确的图像。

例如,单元格1-9将显示Title1-9和正确的Image1-9当滚动缓慢时,单元格19-27将显示Title 19-27,将简要显示Image 10-18,然后显示正确的Image 19-27。 当快速滚动大量的单元格(例如从单元格1-9到单元格90-99)时,单元格90-99将显示标题90-99,将显示图像10-50ish,然后将不正确地保持在图像41-50 (或附近)。 当进一步滚动时,单元格100+将显示正确的标题,但仅显示图像41-50范围内的图像。

我认为这个错误是因为没有正确处理单元重用,图像caching处理不当,或者两者兼而有之。 这也可能是我没有看到作为初学者的iOS /快速程序员的东西。 我试图用完成修饰符实现一个请求,但似乎无法让我的代码设置的方式正常工作。 我将不胜感激任何帮助,以及解释为什么修复程序的工作方式。 谢谢!

相关代码如下。

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! } 

您需要了解如何出队正常工作。 有很好的文章。

总结:

为了保持滚动的平滑性,使用基本上在一定限度之后重新使用细胞的出列。 假设你一次有10个可见的细胞,它可能会创造16-18(3-4以上,3-4以下,只是粗略估计)单元格,即使你可能需要1000个单元格。

现在当你这样做的时候,

 let cell: SeriesCollectionViewCell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! SeriesCollectionViewCell 

您正在重新使用已经生成的单元格中的现有单元格。 这就是为什么你看到旧图像一段时间,然后你看到新的形象,一旦它被加载。

您需要清除旧的图像一旦你出队的单元格。

 cell.seriesImage.image = UIImage() //nil 

你将基本上在图像中设置一个空白的占位符,而不是在你的情况下没有乱搞imageCache为零。

这解决了这个问题 –

值得注意的是,如果您进行慢速或中速滚动,则在将正确的图像渲染到正确的单元格(单元格的标签文本始终正确,只有图像永远closures)之前,会显示不同的图像。

现在当为当前单元格分配图像时,如果分配的图像属于这个单元格,则需要再次检查。 您需要检查这是因为您正在分配的图像视图正在被重用,并且它可能与indexPath中的单元格不同于生成图像加载请求时的单元格。

当你将一个单元格出列并将image属性设置为零时,请让seriesImage为你记住当前的indexPath。

 cell.seriesImage.indexPath = indexPath 

稍后,如果imageView仍然属于之前分配的indexPath,则只将图像分配给它。 这与100%的细胞再利用。

 if cell.seriesImage.indexPath == indexPath cell.seriesImage.image = image 

您可能需要考虑在UIImageView实例上设置indexPath。 这是我准备和使用的一个类似的情况UIView – 增加

它在Objective-C和Swift中都可用。

在您的自定义单元类中实现func prepareForReuse() 。 并在prepareForReuse()函数中重置您的图像。 所以它会在重新使用单元之前重置图像视图。