[英]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.