繁体   English   中英

swift iOS - 快速滚动后混合的UICollectionView图像

[英]swift iOS - UICollectionView images mixed up after fast scroll

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

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时,单元格将抓取不正确的图像。 值得注意的是,如果您进行慢速或中速滚动,则在将正确的图像渲染到正确的单元格之前将显示不同的图像(单元格的标签文本始终是正确的,只有图像永远关闭)。 在图像与具有标签的正确单元格不匹配之后,CollectionView中的所有其他单元格也将显示不正确的图像。

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

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

相关代码如下。

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!

}

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

总结一下:

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

现在当你这样做 -

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

您正在重复使用已制作单元格中的现有单元格。 这就是为什么你会看到旧图像一段时间然后在加载后看到新图像的原因。

一旦出列单元格,您需要清除旧图像。

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

你基本上会在图像中以这种方式设置一个空白占位符,而不是在你的情况下使用imageCache为零。

这解决了这个问题 -

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

现在,在为当前单元格指定图像时,如果要分配的图像属于此单元格,则需要再次检查。 您需要检查这一点,因为您正在重新分配它的图像视图,它可能与indexPath上与生成图像加载请求时不同的单元格相关联。

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

cell.seriesImage.indexPath = indexPath

稍后,如果imageView仍属于先前分配的indexPath,则仅将图像分配给它。 这与细胞重用100%有效。

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

您可能需要考虑在UIImageView实例上设置indexPath。 这是我准备并用于类似场景的东西 - UIView-Additions

它有Objective-C和Swift两种版本。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM