我正在构建一个UITableView,它将具有不同布局的单元格。我遇到问题的单元格中嵌入了从API生成的UICollectionView。
类别名称和ID正确填充在单元格中,但UICollectionView中的图像却没有。图像已加载,但不适用于该类别。 Screen capture of how the collection is loading currently
我尝试过的一些事情:
对每个类别的ID进行硬编码,而不是动态生成它们。当我这样做时,会加载正确的图像(有时但并非总是如此)...并且如果它们加载正确,则在我滚动图像时会更改为错误的图像
prepareForReuse()函数...我不确定我将其放置在什么位置以及将在其中进行重置(我有一些代码,我相信已经有点使图像无效了[下面的代码]) )
我花了几个小时试图解决这个问题,但是我被困住了……任何建议将不胜感激。
我的视图控制器:
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
}
}
集合视图单元格:
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)
}
}
答案 0 :(得分:2)
UICollectionViewCell
和UITableViewCell
都被重用。当滚动离开屏幕顶部时,它将重新插入可见单元的下方,作为将出现在屏幕上的下一个单元。单元保留在此出队/重新出队过程中拥有的所有数据。 prepareForReuse
的存在可以使您将视图重置为默认值,并清除上一次显示该视图以来的所有数据。在处理异步过程(例如网络调用)时,这尤其重要,因为它们可能会超出显示单元格的时间。此外,您正在awakeFromNib
中进行许多非设置工作。每次显示单元格时都不会调用此方法,仅在单元格第一次显示时才调用此方法。如果该单元格不在屏幕上并被重用,则不会调用awakeFromNib
。这很可能是您的收藏夹视图数据错误的主要原因,当它们出现在屏幕上时,它们从未发出网络请求。
EcardsCategoriesTableViewCell:
prepareForReuse
应该被实现。此方法需要发生一些事情:
theseEcards
应该被清除。当表格视图滚动到屏幕之外时,您希望摆脱集合视图数据,否则,下次显示该单元格时,它将可能显示错误单元格的集合视图数据。此外,需要将网络呼叫移出awakeFromNib
:
您仅在awakeFromNib
中进行网络通话。仅在第一次创建单元格时调用此方法。当您重用单元格时,它不会被调用。应使用此方法从笔尖进行任何其他视图设置,但不是将数据添加到单元格的主要入口点。我会在您的单元格上添加一个方法,让您设置类别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
应该被实现,并且其中应该包含以下内容:
在单元格中执行了这些更改之后,您可能会注意到tableview和collection视图感觉很慢。数据不是立即可用的。您需要缓存数据或以某种方式预加载数据。这比该主题的讨论要大,但这是您的下一步。