CollectionViewCell 中标签中的数据有时会在重新加载时刷新,有时则不会

Posted

技术标签:

【中文标题】CollectionViewCell 中标签中的数据有时会在重新加载时刷新,有时则不会【英文标题】:Data From Label in CollectionViewCell Sometimes Refreshes on Reload other times it Doesn't 【发布时间】:2018-12-30 23:38:56 【问题描述】:

首先让我说这似乎是关于 SO 的一个常见问题,我已经阅读了从 Swift 到 Obj-C 的所有帖子。我在过去 9 小时内尝试了很多不同的方法,但我的问题仍然存在。

我有一个带有 collectionView 的 vc (vc1)。在 collectionView 内部,我有一个自定义单元格,其中包含一个标签和一个 imageView。在 cellForItem 内,我有一个属性也在自定义单元格内,当从 datasource[indePath.item] 设置属性时,单元格内有一个属性观察器,用于设置标签和 imageView 的数据。

vc1 中有一个按钮可以推动 vc2,如果用户从 vc2 中选择某些内容,它会通过委托传递回 vc1。 vc2 被弹出。

正确的数据总是被传回(我在调试器中检查了多次)。

问题是如果 vc1 中有一个现有单元格,当新数据添加到数据源时,在我重新加载 collectionView 后,第一个单元格的标签数据现在显示在新单元格的标签上,数据来自新单元格的标签现在显示在来自旧单元格的标签上。

我已经尝试了从 prepareToReuse 到删除标签的所有方法,但由于某种原因,只有单元格的标签数据会混淆。奇怪的是标签有时会正确更新,而有时则不会imageView 总是显示正确的图像,即使标签数据不正确,我也不会遇到任何问题。数据源中的 2 个模型对象始终位于正确的索引位置,并提供正确的信息。

可能是什么问题?

vc1: UIViewController, CollectionViewDataSource & Delegate 

    var datasource = [MyModel]() // has 1 item in it from viewDidLoad

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 

        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: customCell, for: indexPath) as! CustomCell

        cell.priceLabel.text = ""
        cell.cleanUpElements()
        cell.myModel = dataSource[indexPath.item]

        return cell
    

    // delegate method from vc2
    func appendNewDataFromVC2(myModel: MyModel) 

        // show spinner

        datasource.append(myModel) // now has 2 items in it

        // now that new data is added I have to make a dip to fb for some additional information
        firebaseRef.observeSingleEvent(of: .value, with:  (snapshot) in

            if let dict = snapshot.value as? [String: Any] else  

            for myModel in self.datasource 
                myModel.someValue = dict["someValue"] as? String
            

            // I added the gcd timer just to give the loop time to finish just to see if it made a difference
            DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: 

               self.datasource.sort  return $0.postDate > $1.postDate  // Even though this sorts correctly I also tried commenting this out but no difference
               self.collectionView.reloadData()

               // I also tried to update the layout
               self.collectionView.layoutIfNeeded()

               // remove spinner
            
        )
        

CustomCell 下面。这是 myModel 属性观察器内部内容的更简化版本。标签中显示的数据取决于其他数据,并且有一些条件可以确定它。在 cellForItem 中添加所有这些会创建一堆代码,这就是为什么我没有更新其中的数据(或在此处添加)而是选择在单元格中执行此操作。但正如我之前所说,当我检查数据时,它始终是 100% 正确的。属性观察器始终正常工作。

CustomCell: UICollectionViewCell 

    let imageView: UIImageView = 
        let iv = UIImageView()
        iv.translatesAutoresizingMaskIntoConstraints = false
        return iv
    ()

    let priceLabel: UILabel = 
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    ()

    var someBoolProperty = false

    var myModel: MyModel? 
        didSet 

           someBoolProperty = true

           // I read an answer that said try to update the label on the main thread but no difference. I tried with and without the DispatchQueue
           DispatchQueue.main.async  [weak self] in
                self?.priceLabel.text = myModel.price!
                self?.priceLabel.layoutIfNeeded()  // tried with and without this
           

           let url = URL(string: myModel.urlStr!)
           imageView.sd_setImage(with: url!, placeholderImage: UIImage(named: "placeholder"))

           // set imageView and priceLabel anchors
           addSubview(imageView)
           addSubview(priceLabel)

           self.layoutIfNeeded() // tried with and without this
        
    

    override func prepareForReuse() 
        super.prepareForReuse()

        // even though Apple recommends not to clean up ui elements in here, I still tried it to no success
        priceLabel.text = ""
        priceLabel.layoutIfNeeded() // tried with and without this
        self.layoutIfNeeded() // tried with and without this

        // I also tried removing the label with and without the 3 lines above 
        for view in self.subviews 
            if view.isKind(of: UILabel.self) 
                view.removeFromSuperview()
            
        
    

    func cleanUpElements() 
        priceLabel.text = ""
        imageView.image = nil
    

我为添加priceLabel.text = "" 的所有位置添加了 1 个断点(总共 3 个),并且一旦 collectionView 重新加载,断点总是被击中 6 次(数据源中的 2 个对象为 3 次)。prepareForReuse 中的第一次,第二次在cellForItem,第三次在cleanUpElements()

【问题讨论】:

【参考方案1】:

原来我必须重置单元格内的属性。即使单元格被重复使用并且 priceLabel.text 被清除,该属性仍保持其旧的bool 值。一旦我通过 cellForItem 重置它,问题就消失了。

10 小时,smh

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 

    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: customCell, for: indexPath) as! CustomCell

    cell.someBoolProperty = false
    cell.priceLabel.text = ""
    cell.cleanUpElements()
    cell.myModel = dataSource[indexPath.item]

    return cell

【讨论】:

查看单元格中的 prepareForReuse 以便事后清理,而不是在出队时清理。 @FJdeBrienne Apple 建议不要这样做,但如果您查看问题,我确实尝试过。重新阅读这个问题,你会看到 prepareToReuse 里面有清理代码。感谢您的建议 抱歉@LanceSamaria 没有注意到。我对你关于 Apple 不推荐它的声明感到惊讶,因为它是他们的 API 函数,可以在重复使用之前专门准备一个可重复使用的单元格。很高兴你成功了! @FJdeBrienne 在这里阅读更多相关信息:***.com/q/40773208/4833705。新年快乐! 感谢您的提醒,我已经使用 prepareForReuse 执行此操作已有一段时间了,并且没有发现任何不利影响。似乎它与性能有关而不是功能。也祝你新年快乐!

以上是关于CollectionViewCell 中标签中的数据有时会在重新加载时刷新,有时则不会的主要内容,如果未能解决你的问题,请参考以下文章

两个网页中的内容,jQuery怎样把其中一个网页中标签中的内容复制到另一网页中显示出来?

ES6

不能下标“[Int]”的值类型

使用 UITableViewAutomaticDimension 后行高度相同

Activity的启动模式

tailwindcss flex 对齐项目和之间的空间