【问题标题】:UICollectionView cells only display after second call to reloadDataUICollectionView 单元格仅在第二次调用 reloadData 后显示
【发布时间】:2017-03-19 20:04:57
【问题描述】:

我可能正在做一些非常愚蠢的事情,但我一生都无法弄清楚发生了什么......

我想要做的很简单。我有一个UICollectionView,这取决于我从服务器加载的数据。从服务器获取并设置数据后,我在集合视图上调用reloadData()。当我这样做时,numberOfItemsInSection 会以正确的计数被调用,但 cellForItemAt 永远不会被调用,除非我第二次调用 reloadData()(甚至不需要延迟)。

这是大体的想法(虽然我意识到这可能与其他地方的某些竞争条件有关,但也许这足以开始对话):

class MyViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {

    @IBOutlet weak var collectionView: UICollectionView!

    var data = [DataModel]()

    override func viewDidLoad() {
        collectionView.delegate = self
        collectionView.dataSource = self
        collectionView.register(UINib(nibName: "MyCell", bundle: nil), forCellWithReuseIdentifier: "MyCell")
        getData()
    }

    func getData() {
        someAsyncMethodToGetData() { (data) in
            // Using CloudKit so I think this is necessary, but tried it with and without
            DispatchQueue.main.async {
                self.data = data
                self.collectionView.reloadData()
                // This is the only way to get cells to render and for cellForItemAt to be called after data is loaded
                self.collectionView.reloadData()
            }
        }

    // Gets called initially AND after reloadData is called in the data completion closure. First time is 0, next time is number of DataModel objects
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return data.count
    }

    // Only gets called initially (unless I call reloadData() twice)
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let cell = feedCollectionView.dequeueReusableCell(withReuseIdentifier: "MyCell", for: indexPath) as? MyCell else {
            return UICollectionViewCell()
        }

        // Configure the cell with data...

        return cell
    }
}

以下是我已经尝试过的一些事情:

  • 确保在主线程上调用 reloadData()(添加了一个符号断点并调用 Thread.isMainThread 进行验证)
  • reloadData() 移动到按钮操作中,以消除任何线程问题的可能性 - 仍然必须单击按钮两次才能显示单元格
  • 根据我遇到的其他一些 SO 解决方案将更新放在 performBatchUpdates
  • 添加了setNeedsLayoutsetNeedsDisplay(不知道为什么在这种情况下需要这样做,但出于绝望我确实尝试了。

有什么建议吗? ????

更新 1

好的,我想我找到了问题,但我想了解解决问题的最佳方法。它通过调用reloadData 出现,集合视图只会重新加载已经可见的单元格。在调用 getData() 之前,我稍微修改了上面的代码以返回 1 个单元格。当getData() 然后调用reloadData() 时,1 个单元格得到更新(但如果数据数组中有 10 个项目,它仍然只会 [重新] 显示 1 个单元格)。我想这是有道理的,因为reloadData() 只重新加载可见单元格,但是当数组的长度发生变化并且屏幕上仍有显示空间时,这必须是一个常见问题。另外,为什么调用reloadData() 两次可以解决问题并显示从不可见的单元格? ????

Here 是我上面的代码的一个更简单的版本,删除了整个“从服务器获取数据”的想法,并用一个简单的延迟代替。

【问题讨论】:

  • 仅仅因为代码在主线程上执行,并不意味着你的代码在主队列上执行。请使用Dispatch.main.async {} 来确定。你在哪里打电话给getData
  • 我明白了。我在调用完成闭包之前在我的数据层中调用Dispatch.main.async {},但我也将它放在闭包中以确保它。我打电话给getDataviewDidLoad()

标签: ios swift uicollectionview


【解决方案1】:

问题是 UICollectionViewFlowLayout 在更改项目数后没有正确失效。所以collectionView.contentSize 还是老了。你需要做类似的事情

self.collectionView.reloadData()
self.collectionView.collectionViewLayout.invalidateLayout()

这将告诉布局重新计算新数据源。

【讨论】:

  • 行得通!我真的很惊讶这如此艰难,而且没有更多的人遇到过这种情况。似乎是一种常见的模式。哦,好吧,希望这对未来的人们有所帮助,这样他们就不会像我一样花几个小时把头撞在墙上。非常感谢!
  • 您好!我遇到了同样的问题,但即使添加了 invalidateLayout() 仍然会发生同样的事情。我在课堂上有与上面相同的代码。
【解决方案2】:

首先确认delegate和dataSource是否正确添加到collectionView。

第二次确定数据是用断点填充还是简单打印

第三次添加:

    DispatchQueque.main.async{
     self.collection.reloadData()
     self.collection.collectionViewLayout.invalidateLayout()
    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-10
    相关资源
    最近更新 更多