【问题标题】:Why is UICollectionViewDiffableDataSource reloading every cell when nothing has changed?为什么 UICollectionViewDiffableDataSource 在没有任何变化的情况下重新加载每个单元格?
【发布时间】:2021-08-23 00:18:01
【问题描述】:

我创建了以下演示视图控制器以在最小示例中重现该问题。

在这里,我使用 UICollectionViewDiffableDataSource 将相同数据的快照重复应用于相同的集合视图,并且每次重新加载所有单元格时,即使没有任何更改。

我想知道这是否是一个错误,或者我是否“持有错误”。

看起来这个其他用户也有同样的问题,尽管他们没有提供足够的信息来准确地重现这个错误: iOS UICollectionViewDiffableDataSource reloads all data with no changes

编辑:我还发现了一个奇怪的行为 - 如果动画差异是 true,则单元格不会每次都重新加载。

import UIKit

enum Section {
    case all
}

struct Item: Hashable {
    var name: String = ""
    var price: Double = 0.0

    init(name: String, price: Double) {
        self.name = name
        self.price = price
    }
}


class ViewController: UIViewController {
    private let reuseIdentifier = "ItemCell"
    private let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
    private lazy var dataSource = self.configureDataSource()

    private var items: [Item] = [
        Item(name: "candle", price: 3.99),
        Item(name: "cat", price: 2.99),
        Item(name: "dribbble", price: 1.99),
        Item(name: "ghost", price: 4.99),
        Item(name: "hat", price: 2.99),
        Item(name: "owl", price: 5.99),
        Item(name: "pot", price: 1.99),
        Item(name: "pumkin", price: 0.99),
        Item(name: "rip", price: 7.99),
        Item(name: "skull", price: 8.99),
        Item(name: "sky", price: 0.99),
        Item(name: "book", price: 2.99)
    ]

    override func viewDidLoad() {
        super.viewDidLoad()

        // Configure the collection view:
        self.collectionView.backgroundColor = .white
        self.collectionView.translatesAutoresizingMaskIntoConstraints = false
        self.collectionView.register(ItemCollectionViewCell.self, forCellWithReuseIdentifier: self.reuseIdentifier)
        self.collectionView.dataSource = self.dataSource
        self.view.addSubview(self.collectionView)
        NSLayoutConstraint.activate([
            self.collectionView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
            self.collectionView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
            self.collectionView.topAnchor.constraint(equalTo: self.view.topAnchor),
            self.collectionView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
        ])

        // Configure the layout:
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1/3), heightDimension: .fractionalHeight(1))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalWidth(1/3))
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
        let section = NSCollectionLayoutSection(group: group)
        let layout = UICollectionViewCompositionalLayout(section: section)
        self.collectionView.setCollectionViewLayout(layout, animated: false)

        // Update the snapshot:
        self.updateSnapshot()

        // Update the snapshot once a second:
        Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
            self?.updateSnapshot()
        }
    }

    func configureDataSource() -> UICollectionViewDiffableDataSource<Section, Item> {
        let dataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: self.collectionView) { (collectionView, indexPath, item) -> UICollectionViewCell? in
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: self.reuseIdentifier, for: indexPath) as! ItemCollectionViewCell
            cell.configure(for: item)
            return cell
        }

        return dataSource
    }

    func updateSnapshot(animatingChange: Bool = false) {

        // Create a snapshot and populate the data
        var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
        snapshot.appendSections([.all])
        snapshot.appendItems(self.items, toSection: .all)

        self.dataSource.apply(snapshot, animatingDifferences: false)
    }
}

class ItemCollectionViewCell: UICollectionViewCell {
    private let nameLabel = UILabel()
    private let priceLabel = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)

        self.addSubview(self.nameLabel)
        self.addSubview(self.priceLabel)

        self.translatesAutoresizingMaskIntoConstraints = false
        self.nameLabel.translatesAutoresizingMaskIntoConstraints = false
        self.nameLabel.textAlignment = .center
        self.priceLabel.translatesAutoresizingMaskIntoConstraints = false
        self.priceLabel.textAlignment = .center
        NSLayoutConstraint.activate([
            self.nameLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor),
            self.nameLabel.topAnchor.constraint(equalTo: self.topAnchor),
            self.nameLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor),
            self.priceLabel.topAnchor.constraint(equalTo: self.nameLabel.bottomAnchor),
            self.priceLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor),
            self.priceLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor),
        ])
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func configure(for item: Item) {
        print("Configuring cell for item \(item)")
        self.nameLabel.text = item.name
        self.priceLabel.text = "$\(item.price)"
    }
}

【问题讨论】:

    标签: ios uicollectionview uicollectionviewdiffabledatasource


    【解决方案1】:

    我想你已经把手指放在上面了。当您说animatingDifferences 将成为false 时,您是在要求可区分数据源表现得好像它不是是可区分数据源。你是在说:“跳过所有可区分的东西,只接受这些新数据。”换句话说,你说的是reloadData()没有创建新的单元格(通过记录很容易证明这一点),因为所有单元格都已经可见;但出于同样的原因,所有可见单元格都被重新配置,这正是人们对reloadData() 的期望。

    另一方面,当animatingDifferencestrue 时,diffable 数据源会认真思考发生了什么变化,以便在必要时对其进行动画处理。因此,由于所有这些在幕后工作,它知道何时可以避免重新加载单元格(因为它可以改为移动单元格)。

    确实,当animatingDifferencestrue 时,您可以应用反转单元格的快照,但永远不会再次调用configure,因为移动单元格是所有需要的待办事项:

    func updateSnapshot(animatingChange: Bool = true) {
        var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
        snapshot.appendSections([.all])
        self.items = self.items.reversed()
        snapshot.appendItems(self.items, toSection: .all)
        self.dataSource.apply(snapshot, animatingDifferences: animatingChange)
    }
    

    有趣的是,我还用shuffled 代替reversed 尝试了上述方法,我发现有时 某些单元格被重新配置。显然,diffable 数据源的主要意图不是不重新加载单元格;这只是一种副作用。

    【讨论】:

    • 哦!有趣 - 很高兴知道!我已经接受了您的回答,尽管它可能会错误地重新配置未更改的单元格,这有点令人沮丧。 RxDataSources / IGListKit(启发可区分数据源的开源库)没有这个问题。
    • 我不知道那些库。我同意一些可区分的数据源似乎相当有问题。
    • 我应该补充一点:您的configure 实施可以从查看是否需要完成任何工作开始。
    猜你喜欢
    • 2020-09-05
    • 1970-01-01
    • 2014-06-22
    • 2019-04-01
    • 1970-01-01
    • 2018-07-31
    • 2016-08-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多