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

时间:2016-06-13 05:55:01

标签: ios swift image custom-cell collectionview

我是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中的所有其他单元格也将显示不正确的图像。

E.g。单元格1-9将显示具有正确Image1-9的Title1-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!

}

2 个答案:

答案 0 :(得分:13)

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

总结:

  

为了保持滚动平滑性,基本上使用出列   在一定限度后重新使用单元格。假设你有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&amp;迅速的。

答案 1 :(得分:3)

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