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

标签 ios swift uitableview uicollectionview

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

最佳答案

UICollectionViewCellUITableViewCell 都被重用了。当一个人滚出屏幕顶部时,它会重新插入可见单元格下方,作为将出现在屏幕上的下一个单元格。单元保留在此出队/重新排队过程中它们拥有的任何数据。 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/

相关文章:

ios - 类型不符合协议(protocol) 'Encodable'

ios - 有什么办法不让 CPU 在后台受到限制?

ios - 在 Objective C 中检查 Swift ViewController 的 isKindOfClass 方法

ios - 如何返回 numberOfRowsInSection?

c# - 如何在c# monotuch中打印多个页面的UITableview内容数据

ios - Swift:按下按钮后,如何将来自三个独立文本字段的数据发布到自定义单元格中的标签上?

ios - UISearchControllerDelegate - 搜索栏在表头中不可见

swift - OSX 应用程序 : how to make window maximized?

objective-c - 首次加载时某些单元格的 UITableViewCell 高度错误

iphone - UITableview 中类似 Tweetie 2/Facebook 的滑动手势?