Bre*_*ski 1 uitableview uiimage swift
我UITableView列出了其中包含图像的社交媒体帖子。一旦加载了所有帖子详细信息并缓存了图像,它看起来不错,但在加载时,它通常会显示带有错误帖子的错误图像。
几个月来我一直在努力并回到这个问题。我不认为这是一个加载问题,它几乎看起来像 iOS 将图像转储到任何旧单元格,直到找到正确的单元格,但老实说我没有想法。
这是我的图像扩展,它也负责缓存:
let imageCache = NSCache<NSString, AnyObject>()
extension UIImageView {
func loadImageUsingCacheWithUrlString(_ urlString: String) {
self.image = UIImage(named: "loading")
if let cachedImage = imageCache.object(forKey: urlString as NSString) as? UIImage {
self.image = cachedImage
return
}
//No cache, so create new one and set image
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!, completionHandler: { (data, response, error) in
if let error = error {
print(error)
return
}
DispatchQueue.main.async(execute: {
if let downloadedImage = UIImage(data: data!) {
imageCache.setObject(downloadedImage, forKey: urlString as NSString)
self.image = downloadedImage
}
})
}).resume()
}
}
Run Code Online (Sandbox Code Playgroud)
这是我的缩短版本UITableView:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let postImageIndex = postArray [indexPath.row]
let postImageURL = postImageIndex.postImageURL
let cell = tableView.dequeueReusableCell(withIdentifier: "FeedItem", for: indexPath) as! FeedItem
cell.delegate = self
cell.postHeroImage.loadImageUsingCacheWithUrlString(postImageURL)
cell.postTitle.text = postArray [indexPath.row].postTitle
cell.postDescription.text = postArray [indexPath.row].postBody
return cell
}
Run Code Online (Sandbox Code Playgroud)
FeedItem 类包括 prepareForReuse() 并且看起来像这样:
override func prepareForReuse() {
super.prepareForReuse()
self.delegate = nil
self.postHeroImage.image = UIImage(named: "loading")
}
Run Code Online (Sandbox Code Playgroud)
编辑:这是我从 Firebase 检索数据的方法:
func retrievePosts () {
let postDB = Database.database().reference().child("MyPosts")
postDB.observe(.childAdded) { (snapshot) in
let snapshotValue = snapshot.value as! Dictionary <String,AnyObject>
let postID = snapshotValue ["ID"]!
let postTitle = snapshotValue ["Title"]!
let postBody = snapshotValue ["Description"]!
let postImageURL = snapshotValue ["TitleImage"]!
let post = Post()
post.postTitle = postTitle as! String
post.postBody = postBody as! String
post.postImageURL = postImageURL as! String
self.configureTableView()
}
}
Run Code Online (Sandbox Code Playgroud)
UITableView显示项目集合时仅使用少数单元格(~屏幕上可见单元格的最大数量),因此您将拥有比单元格更多的项目。这是因为 table view 重用机制,这意味着相同的UITableViewCell实例将用于显示不同的项目。图像出现问题的原因是您没有正确处理单元重用。
在cellForRowAt你调用的函数中:
cell.postHeroImage.loadImageUsingCacheWithUrlString(postImageURL)
Run Code Online (Sandbox Code Playgroud)
当您滚动表格视图时,cellForRowAt将在同一单元格的不同调用中调用此函数,但(最有可能)显示不同项目的内容(因为单元格重用)。
假设 X 是您正在重用的单元格,那么这些是将被调用的大致函数:
1. X.prepareForReuse()
// inside cellForRowAt
2. X.postHeroImage.loadImageUsingCacheWithUrlString(imageA)
// at this point the cell is configured for displaying the content for imageA
// and later you reuse it for displaying the content of imageB
3. X.prepareForReuse()
// inside cellForRowAt
4. X.postHeroImage.loadImageUsingCacheWithUrlString(imageB)
Run Code Online (Sandbox Code Playgroud)
缓存图像后,您将始终按该顺序有 1、2、3 和 4,这就是为什么在这种情况下您看不到任何问题的原因。但是,下载图像并将其设置为图像视图的代码在单独的线程中运行,因此不再保证该顺序。而不是只有上面的四个步骤,你将有类似的东西:
1. X.prepareForReuse()
// inside cellForRowAt
2. X.postHeroImage.loadImageUsingCacheWithUrlString(imageA)
// after download finishes
2.1 X.imageView.image = downloadedImage
// at this point the cell is configured for displaying the content for imageA
// and later you reuse it for displaying the content of imageB
3. X.prepareForReuse()
// inside cellForRowAt
4. X.postHeroImage.loadImageUsingCacheWithUrlString(imageB)
4.1 X.imageView.image = downloadedImage
Run Code Online (Sandbox Code Playgroud)
在这种情况下,由于并发,您可能会遇到以下情况:
为了解决这个问题,让我们把注意力转向loadImageUsingCacheWithUrlString函数内部有问题的一段代码:
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!, completionHandler: { (data, response, error) in
DispatchQueue.main.async(execute: {
if let downloadedImage = UIImage(data: data!) {
imageCache.setObject(downloadedImage, forKey: urlString as NSString)
// this is the line corresponding to 2.1 and 4.1 above
self.image = downloadedImage
}
})
}).resume()
Run Code Online (Sandbox Code Playgroud)
如您所见,self.image = downloadedImage即使不再显示与该图像关联的内容,您也正在设置,因此您需要某种方法来检查是否仍然如此。由于您loadImageUsingCacheWithUrlString在 for 的扩展中定义UIImageView,因此您没有太多上下文可以知道是否应该显示图像。取而代之的是,我建议将该函数移动到UIImage将在完成处理程序中返回该图像的扩展,然后从您的单元格内部调用该函数。它看起来像:
extension UIImage {
static func loadImageUsingCacheWithUrlString(_ urlString: String, completion: @escaping (UIImage) -> Void) {
if let cachedImage = imageCache.object(forKey: urlString as NSString) as? UIImage {
completion(cachedImage)
}
//No cache, so create new one and set image
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!, completionHandler: { (data, response, error) in
if let error = error {
print(error)
return
}
DispatchQueue.main.async(execute: {
if let downloadedImage = UIImage(data: data!) {
imageCache.setObject(downloadedImage, forKey: urlString as NSString)
completion(downloadedImage)
}
})
}).resume()
}
}
class FeedItem: UITableViewCell {
// some other definitions here...
var postImageURL: String? {
didSet {
if let url = postImageURL {
self.image = UIImage(named: "loading")
UIImage.loadImageUsingCacheWithUrlString(url) { image in
// set the image only when we are still displaying the content for the image we finished downloading
if url == postImageURL {
self.imageView.image = image
}
}
}
else {
self.imageView.image = nil
}
}
}
}
// inside cellForRowAt
cell.postImageURL = postImageURL
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1215 次 |
| 最近记录: |