UICollectionView在单元格中显示错误的图像

时间:2018-07-06 14:54:41

标签: ios swift uitableview uicollectionview

我正在构建一个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)
}
}

1 个答案:

答案 0 :(得分:2)

UICollectionViewCellUITableViewCell都被重用。当滚动离开屏幕顶部时,它将重新插入可见单元的下方,作为将出现在屏幕上的下一个单元。单元保留在此出队/重新出队过程中拥有的所有数据。 prepareForReuse的存在可以使您将视图重置为默认值,并清除上一次显示该视图以来的所有数据。在处理异步过程(例如网络调用)时,这尤其重要,因为它们可能会超出显示单元格的时间。此外,您正在awakeFromNib中进行许多非设置工作。每次显示单元格时都不会调用此方法,仅在单元格第一次显示时才调用此方法。如果该单元格不在屏幕上并被重用,则不会调用awakeFromNib。这很可能是您的收藏夹视图数据错误的主要原因,当它们出现在屏幕上时,它们从未发出网络请求。

EcardsCategoriesTableViewCell:

prepareForReuse应该被实现。此方法需要发生一些事情:

  • theseEcards应该被清除。当表格视图滚动到屏幕之外时,您希望摆脱集合视图数据,否则,下次显示该单元格时,它将可能显示错误单元格的集合视图数据。
  • 您应该保留对awakeFromNib中运行的dataTask的引用,然后在prepareForReuse中对此dataTask调用cancel。无需执行此操作,该单元就可以显示,消失,然后在dataTask完成之前重新使用。如果是这种情况,则可以用以前的dataTask(本应在滚动到屏幕之外的单元格上运行的那个)中的值替换预期的值。

此外,需要将网络呼叫移出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应该被实现,并且其中应该包含以下内容:

  • 应清除图像视图上的图像或将其设置为默认图像。
  • 图像请求应被取消。这将需要一些重构才能完成。您需要在集合视图单元格中保留对dataTask的引用,以便可以在适当时取消它。

在单元格中执行了这些更改之后,您可能会注意到tableview和collection视图感觉很慢。数据不是立即可用的。您需要缓存数据或以某种方式预加载数据。这比该主题的讨论要大,但这是您的下一步。