我是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!
}
答案 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()函数中重置您的图像。所以它会在重用单元格之前重置图像视图。