[英]UICollectionView displays wrong images in cells
I am building a UITableView that is going to have cells with different layouts in them. 我正在构建一个UITableView,它将具有不同布局的单元格。 The cell I am having issues with has a UICollectionView embedded in it that is generated from an API.
我遇到问题的单元格中嵌入了从API生成的UICollectionView。
The category name and id populate in the cell correctly, but the images in the UICollectionView do not. 类别名称和ID正确填充在单元格中,但UICollectionView中的图像却没有。 The images load, but they are not the right ones for that category.
图像已加载,但不适用于该类别。 Screen capture of how the collection is loading currently
该集合当前如何加载的屏幕截图
Some of the things I've tried: 我尝试过的一些方法:
Hard-coding the ids for each one of the categories instead of dynamically generating them. 对每个类别的ID进行硬编码,而不是动态生成它们。 When I do this, the correct images load (sometimes but not always) ... and if they do load correctly, when I scroll the images change to wrong ones
当我这样做时,加载正确的图像(有时但并非总是如此)...并且如果加载正确,当我滚动图像时,图像将变为错误的图像
The prepareForReuse() function ... I'm not exactly sure where I would put it and what I would reset in it (I have code I believe already kind of nils the image out [code included below]) prepareForReuse()函数...我不确定是否将其放置在什么位置以及将在其中进行重置(我有代码,我相信已经有点使图像无效了[下面的代码])
I have spent a few hours trying to figure this out, but I am stuck ... any suggestions would be appreciated. 我花了几个小时试图解决这个问题,但是我被困住了……任何建议将不胜感激。
My View Controller: 我的视图控制器:
class EcardsViewController: BaseViewController {
@IBOutlet weak var categoryTable: UITableView!
var categories = [CategoryItem]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.categoryTable.dataSource! = self
self.categoryTable.delegate! = self
DispatchQueue.main.async {
let jsonUrlString = "https://*********/******/category"
guard let url = URL(string: jsonUrlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
if err == nil {
do {
let decoder = JSONDecoder()
let ecardcategory = try decoder.decode(Category.self, from: data)
self.categories = ecardcategory.category
self.categories.sort(by: {$0.title < $1.title})
self.categories = self.categories.filter{$0.isFeatured}
} catch let err {
print("Err", err)
}
DispatchQueue.main.async {
self.categoryTable.reloadData()
}
}
}.resume()
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension EcardsViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return categories.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "EcardsCategoriesTableViewCell", for: indexPath) as! EcardsCategoriesTableViewCell
cell.categoryName.text = ("\(categories[indexPath.row].title)**\(categories[indexPath.row].id)")
cell.ecardCatId = String(categories[indexPath.row].id)
return cell
}
}
My Table Cell: 我的表格单元格:
class EcardsCategoriesTableViewCell: UITableViewCell {
@IBOutlet weak var categoryName: UILabel!
@IBOutlet weak var thisEcardCollection: UICollectionView!
var ecardCatId = ""
var theseEcards = [Content]()
let imageCache = NSCache<NSString,AnyObject>()
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
self.thisEcardCollection.dataSource! = self
self.thisEcardCollection.delegate! = self
DispatchQueue.main.async {
let jsonUrlString = "https://**********/*******/content?category=\(self.ecardCatId)"
guard let url = URL(string: jsonUrlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
if err == nil {
do {
let decoder = JSONDecoder()
let ecards = try decoder.decode(Ecards.self, from: data)
self.theseEcards = ecards.content
self.theseEcards = self.theseEcards.filter{$0.isActive}
} catch let err {
print("Err", err)
}
DispatchQueue.main.async {
self.thisEcardCollection.reloadData()
}
}
}.resume()
}
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
extension EcardsCategoriesTableViewCell: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return theseEcards.count > 7 ? 7 : theseEcards.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "EcardCategoriesCollectionViewCell", for: indexPath) as! EcardCategoriesCollectionViewCell
cell.ecardImage.contentMode = .scaleAspectFill
let ecardImageLink = theseEcards[indexPath.row].thumbSSL
cell.ecardImage.downloadedFrom(link: ecardImageLink)
return cell
}
}
Collection View Cell: 集合视图单元格:
class EcardCategoriesCollectionViewCell: UICollectionViewCell {
@IBOutlet weak var ecardImage: UIImageView!
}
Extension to "download" image: “下载”图片的扩展名:
extension UIImageView {
func downloadedFromReset(url: URL, contentMode mode: UIViewContentMode = .scaleAspectFit, thisurl: String) {
contentMode = mode
self.image = nil
// check cache
if let cachedImage = ImageCache.shared.image(forKey: thisurl) {
self.image = cachedImage
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let mimeType = response?.mimeType, mimeType.hasPrefix("image"),
let data = data, error == nil,
let image = UIImage(data: data)
else { return }
ImageCache.shared.save(image: image, forKey: thisurl)
DispatchQueue.main.async() {
self.image = image
}
}.resume()
}
func downloadedFrom(link: String, contentMode mode: UIViewContentMode = .scaleAspectFit) {
guard let url = URL(string: link) else { return }
downloadedFromReset(url: url, contentMode: mode, thisurl: link)
}
}
Both UICollectionViewCell
and UITableViewCell
are reused. UICollectionViewCell
和UITableViewCell
都被重用。 As one scrolls off the top of the screen, it is reinserted below the visible cells as the next cell that will appear on screen. 当滚动离开屏幕顶部时,它将重新插入可见单元的下方,作为将出现在屏幕上的下一个单元。 The cells retain any data that they have during this dequeuing/requeuing process.
单元保留在此出队/重新出队过程中拥有的所有数据。
prepareForReuse
exists to give you a point to reset the view to default values and to clear any data from the last time it was displayed. prepareForReuse
存在使您可以将视图重置为默认值,并清除上次显示该视图以来的所有数据。 This is especially important when working with asynchronous processes, such as network calls, as they can outlive the amount of time that a cell is displayed. 在处理异步过程(例如网络调用)时,这尤其重要,因为它们可能会超出显示单元格的时间。 Additionally, you're doing a lot of non-setup work in
awakeFromNib
. 此外,您在
awakeFromNib
了许多非设置工作。 This method is not called every time a cell is displayed, it is only called the FIRST time a cell is displayed. 每次显示单元格时都不会调用此方法,仅在单元格第一次显示时才调用此方法。 If that cell goes off screen and is reused,
awakeFromNib
is not called. 如果该单元格不在屏幕上并被重用,则不会调用
awakeFromNib
。 This is likely a big reason that your collection views have the wrong data, they're never making their network request when they appear on screen. 这很可能是您的收藏夹视图数据错误的主要原因,当它们出现在屏幕上时,它们从未发出网络请求。
EcardsCategoriesTableViewCell: EcardsCategoriesTableViewCell:
prepareForReuse
should be implemented. prepareForReuse
应该被实现。 A few things need to occur in this method: 此方法需要发生一些事情:
theseEcards
should be nilled. theseEcards
应该被清除。 When a table view scrolls off screen, you want to get rid of the collection view data or else the next time that cell is displayed, it will show the collection view data potentially for the wrong cell. Additionally, the network call needs to be moved out of awakeFromNib
: 此外,需要将网络呼叫移出
awakeFromNib
:
You are only ever making the network call in awakeFromNib
. 您只在
awakeFromNib
进行网络呼叫。 This method only gets called the first time a cell is created. 仅在第一次创建单元格时调用此方法。 When you reuse a cell, it is not called.
当您重用单元格时,它不会被调用。 This method should be used to do any additional setup of views from the nib, but is not your main entry point in adding data to a cell.
应使用此方法从笔尖进行任何其他视图设置,但不是将数据添加到单元格的主要入口点。 I would add a method on your cell that lets you set the category id.
我会在您的单元格上添加一个方法,让您设置类别ID。 This will make the network request.
这将发出网络请求。 It will look something like this:
它看起来像这样:
func setCategoryId(_ categoryId: String) { DispatchQueue.main.async { let jsonUrlString = "https://**********/*******/content?category=\\(categoryId)" guard let url = URL(string: jsonUrlString) else { return } URLSession.shared.dataTask(with: url) { (data, response, err) in guard let data = data else { return } if err == nil { do { let decoder = JSONDecoder() let ecards = try decoder.decode(Ecards.self, from: data) self.theseEcards = ecards.content self.theseEcards = self.theseEcards.filter{$0.isActive} } catch let err { print("Err", err) } DispatchQueue.main.async { self.thisEcardCollection.reloadData() } } }.resume() } }
This will be called in the cellForRowAt dataSource method in EcardsViewController
. 这将在
EcardsViewController
的cellForRowAt dataSource方法中EcardsViewController
。
EcardCategoriesCollectionViewCell
: EcardCategoriesCollectionViewCell
:
This cell has similar issues. 这个单元有类似的问题。 You are setting images asynchronously, but are not clearing the images and cancelling the network requests when the cell is going to be reused.
您正在异步设置图像,但是在要重用该单元时不会清除图像并取消网络请求。
prepareForReuse
should be implemented and the following should occur within it: prepareForReuse
应该实现,并且其中应该包含以下内容:
After implementing these changes in the cells, you'll likely notice that the tableview and collection view feel slow. 在单元格中实现了这些更改之后,您可能会注意到tableview和collection视图感觉很慢。 Data isn't instantly available.
数据不是立即可用的。 You'll want to cache the data or preload it some way.
您需要缓存数据或以某种方式预加载数据。 That is a bigger discussion than is right for this thread, but it will be your next step.
这比该主题的讨论要大得多,但这将是您的下一步。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.