我正在构建一个 UITableView,其中将包含具有不同布局的单元格。我遇到问题的单元格中嵌入了一个从 API 生成的 UICollectionView。
类别名称和 ID 正确填充到单元格中,但 UICollectionView 中的图像没有。图像加载,但它们不适合该类别。 Screen capture of how the collection is loading currently
我尝试过的一些事情:
对每个类别的 ID 进行硬编码,而不是动态生成它们。当我这样做时,正确的图像加载(有时但不总是)......如果它们正确加载,当我滚动图像时,图像会变为错误的图像
prepareForReuse() 函数......我不确定我会把它放在哪里以及我会在其中重置什么(我有代码我相信已经有点 nils 图像 [下面包含的代码] )
我花了几个小时试图解决这个问题,但我被卡住了......任何建议都会受到赞赏。
我的 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
}
}
我的表格单元格:
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 单元格:
class EcardCategoriesCollectionViewCell: UICollectionViewCell {
@IBOutlet weak var ecardImage: UIImageView!
}
“下载”图像的扩展:
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)
}
}
最佳答案
UICollectionViewCell
和 UITableViewCell
都被重用了。当一个人滚出屏幕顶部时,它会重新插入可见单元格下方,作为将出现在屏幕上的下一个单元格。单元保留在此出队/重新排队过程中它们拥有的任何数据。 prepareForReuse
的存在是为了让您可以将 View 重置为默认值并清除上次显示时的所有数据。这在使用异步进程(例如网络调用)时尤为重要,因为它们的生命周期可能超过显示单元格的时间。此外,您在 awakeFromNib
中进行了大量非设置工作。每次显示单元格时不会调用此方法,它只会在第一次显示单元格时调用。如果该单元格离开屏幕并被重复使用,则不会调用 awakeFromNib
。这可能是您的 Collection View 包含错误数据的一个重要原因,当它们出现在屏幕上时,它们从不发出网络请求。
EcardsCategoriesTableViewCell:
prepareForReuse
应该被实现。此方法中需要发生一些事情:
theseEcards
应该为空。当表格 View 滚出屏幕时,您希望摆脱 Collection View 数据,否则下次显示该单元格时,它可能会显示错误单元格的 Collection View 数据。- 您应该保留对在 awakeFromNib 中运行的 dataTask 的引用,然后在 prepareForReuse 中对该 dataTask 调用取消。如果不这样做,单元格可以显示、消失,然后在 dataTask 完成之前被重新使用。如果是这种情况,它可能会将预期值替换为上一个 dataTask 中的值(应该在滚动到屏幕外的单元格上运行的值)。
此外,网络调用需要移出awakeFromNib
:
您只会在
awakeFromNib
中进行网络调用。只有在第一次创建单元格时才会调用此方法。当您重用一个单元格时,它不会被调用。此方法应该用于从 nib 执行任何额外的 View 设置,但不是向单元格添加数据的主要入口点。我会在您的单元格上添加一个方法,让您设置类别 ID。这将发出网络请求。它看起来像这样: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() } }
这将在 EcardsViewController
的 cellForRowAt dataSource 方法中调用。
EcardCategoriesCollectionViewCell
:
这个单元格有类似的问题。您正在异步设置图像,但不会在要重新使用单元格时清除图像并取消网络请求。 prepareForReuse
应该被实现并且下面应该发生在里面:
- 应清除 ImageView 中的图像或将其设置为默认图像。
- 应该取消图像请求。这将需要一些重构来完成。您需要在 Collection View 单元格中保留对 dataTask 的引用,以便您可以在适当的时候取消它。
在单元格中实现这些更改后,您可能会注意到 TableView 和 Collection View 感觉很慢。数据不是立即可用的。您需要缓存数据或以某种方式预加载数据。这是一个比适合本主题的讨论更大的讨论,但这将是您的下一步。
关于ios - UICollectionView 在单元格中显示错误的图像,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51213031/