【发布时间】:2016-10-13 10:52:49
【问题描述】:
我是 swift 和 iOS 编程的新手,但我已经能够在 Xcode 中为我的应用程序构建一个基本稳定的启动界面。 CollectionView 从我的家庭网络服务器上的 csv 文件创建的字典数组中抓取图像和文本。 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 中快速滚动时,单元格会抓取不正确的图像。值得注意的是,如果您进行慢速或中等滚动,则会在正确的图像渲染到正确的单元格之前显示不同的图像(单元格的标签文本始终正确,只有图像永远关闭)。发生图像与带标签的正确单元格不匹配后,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!
}
【问题讨论】:
-
单元被重用,并且您正在异步调度图像提取,因此当您使用单元并异步获取新图像时,旧的提取仍在运行。你需要处理这个。可能最简单的方法是使用 SDWebImage,它在 UIImageView 上有一个扩展,可以为你处理这个
-
@Paulw11:是的,我认为这与未正确取消请求或其他原因有关。我希望仅使用 UIKit 编写代码,因为我将此应用程序用作学习体验,并且我觉得依赖外部框架会破坏该目的,因为它是一种快捷方式。不过,我确实研究了 Alamofire/AlamofireImage 和 SDWebImage,但由于某种原因,在安装 pod 后,我无法导入模块(这是一个我还没有完全弄清楚的单独问题)。如果需要,我会使用它,只是想先尝试用 UIKit 处理它。
-
另一种方法是将图像 URL 存储为单元格的属性,然后在完成闭包中,根据您刚刚获取的 URL 检查属性值。如果不匹配,则不要设置图像,因为该单元格已被重复使用。
标签: ios swift image custom-cell collectionview