简体   繁体   English

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

[英]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. UICollectionViewCellUITableViewCell都被重用。 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. 当表格视图滚动到屏幕之外时,您想要摆脱集合视图数据,否则,下次显示该单元格时,它将显示可能错误的单元格的集合视图数据。
  • You should keep a reference to the dataTask that runs in awakeFromNib and then call cancel on this dataTask in prepareForReuse. 您应该保留对在awakeFromNib中运行的dataTask的引用,然后在prepareForReuse中对此dataTask调用cancel。 Without doing this, the cell can display, disappear, then get reused before the dataTask completes. 无需执行此操作,该单元就可以显示,消失,然后在dataTask完成之前重新使用。 If that is the case, it may replace the intended values with the values from the previous dataTask (the one that was supposed to run on the cell that was scrolled off screen). 如果是这种情况,它可以用先前的dataTask(本应在滚动到屏幕之外的单元格上运行的那个)中的值替换预期的值。

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应该实现,并且其中应该包含以下内容:

  • The image on the image view should be cleared or set to a default image. 图像视图上的图像应被清除或设置为默认图像。
  • The image request should be cancelled. 图片请求应被取消。 This is going to take some refactoring to accomplish. 这将需要一些重构才能完成。 You need to hold a reference to the dataTask in the collection view cell so that you can cancel it when appropriate. 您需要在集合视图单元格中保留对dataTask的引用,以便可以在适当时取消引用。

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM