使用 UICollectionViewFlowLayout / UICollectionViewLayout 子类,不会在正确的时间调用 CellForItemAtIndexPath

Posted

技术标签:

【中文标题】使用 UICollectionViewFlowLayout / UICollectionViewLayout 子类,不会在正确的时间调用 CellForItemAtIndexPath【英文标题】:With UICollectionViewFlowLayout / UICollectionViewLayout subclass, CellForItemAtIndexPath is not called at the correct time 【发布时间】:2016-04-04 20:16:51 【问题描述】:

这个问题不是重复的,尽管我在 stavkOverflow 上看到很多类似的问题。这是我的代码周围的情况:

    CellForItemAtIndexPath - 如果我的集合视图位于 UIStackView 的外部,则正确调用。只有在 stackView 内部时才会出现问题

    我已正确地将我的 flowLayout 从 UICollectionViewFlowLayout 子类化。

    CollectionView 正确返回 numberOfSections 和每个部分中的 NumberOfItems。每个部分的项目数为 42(6 行 x 7 列)

这是问题的样子:您可以看到它会到达不调用 CellForItemAtIndexPath 方法的地步。那里不会有细胞。然后突然,当你滚动到某个点时,它会立即被调用,导致单元格出现。

有人知道错误可能是什么吗?如果 UICollectionView 未放置在 UIStackView 中,则不会发生此错误。

[编辑]

好的。我已将问题缩小到 UICollectionViewFlowLayout 子类。那里有一小行代码可以将 collectionView 的布局更改为水平。意思是,视图上的单元格通常是这样渲染的:

[1][4][7]
[2][5][8]
[3][6][9]

//But the code should render it like this
[1][2][3]
[4][5][6]
[7][8][9]

我有以下代码来执行此操作(取自 KDCalendar)。这段代码工作正常,但一旦它在 stackView 中,它会查看上面显示的内容。在stackView中更改单元格顺序的方法是否不同?以下是导致问题的代码(仅在stackView内部)

override  public func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? 
    if let attrs = super.layoutAttributesForItemAtIndexPath(indexPath) 
        let attrscp = attrs.copy() as! UICollectionViewLayoutAttributes
        self.applyLayoutAttributes(attrscp)
        return attrscp
    
    return nil


override public func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? 
    return super.layoutAttributesForElementsInRect(rect)?.map 
        attrs in
        let attrscp = attrs.copy() as! UICollectionViewLayoutAttributes
        self.applyLayoutAttributes(attrscp)
        return attrscp
    


func applyLayoutAttributes(attributes : UICollectionViewLayoutAttributes) 
    if attributes.representedElementKind != nil return
    if let collectionView = self.collectionView 
        let stride = (self.scrollDirection == .Horizontal) ? collectionView.frame.size.width : collectionView.frame.size.height
        let offset = CGFloat(attributes.indexPath.section) * stride
        var xCellOffset : CGFloat = CGFloat(attributes.indexPath.item % 7) * self.itemSize.width
        var yCellOffset : CGFloat = CGFloat(attributes.indexPath.item / 7) * self.itemSize.height
        if(self.scrollDirection == .Horizontal) 
            xCellOffset += offset;
         else 
            yCellOffset += offset
        
        attributes.frame = CGRectMake(xCellOffset, yCellOffset, self.itemSize.width, self.itemSize.height)
    

所以我想基本上我的问题是,什么代码是通过子类化 UICollectionViewFlowLayout 来水平渲染单元格的最快最有效的方法?我的 collectionView 将始终有 7 列。这些行只能是 1、2、3 或 6。

[编辑]

我已将问题缩小到以下功能:

func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? 
    //Inside this function, I have put the following code to test it.
    print(super.layoutAttributesForElementsInRect(rect))

这是我得到的打印输出

[0] : 索引路径: (length = 2, path = 0 - 0);框架 = (0 0; 59.1429 72.8333); 1 : 索引路径: ( length = 2, path = 0 - 1); 帧 = (0 73; 59.1429 72.8333); [2] : 索引路径: ( length = 2, path = 0 - 2); 帧 = (0 145.667; 59.1429 72.8333); [3] : 索引路径: ( length = 2, path = 0 - 3); 帧 = (0 218.667; 59.1429 72.8333); [4] : 索引路径: ( length = 2, path = 0 - 4); 帧 = (0 291.333; 59.1429 72.8333); [5] : 索引路径: (length = 2, path = 0 - 5); 帧 = (0 364.333; 59.1429 72.8333); [6] : 索引路径: ( length = 2, path = 0 - 6); 帧 = (59 0; 59.1429 72.8333); [7] : 索引路径: ( length = 2, path = 0 - 7); 帧 = (59 73; 59.1429 72.8333); [8] : 索引路径: ( length = 2, path = 0 - 8); 帧 = (59 145.667; 59.1429 72.8333); [9] : 索引路径: ( length = 2, path = 0 - 9); 帧 = (59 218.667; 59.1429 72.8333); [10] : 索引路径: (length = 2, path = 0 - 10); 帧 = (59 291.333; 59.1429 72.8333); [11] : 索引路径: (length = 2, path = 0 - 11); 帧 = (59 364.333; 59.1429 72.8333); [12] : 索引路径: (length = 2, path = 0 - 12); 帧 = (118.333 0; 59.1429 72.8333); [13] : 索引路径: (length = 2, path = 0 - 13); 帧=(118.333 73;59.1429 72.8333); [14] : 索引路径: (length = 2, path = 0 - 14); 帧=(118.333 145.667;59.1429 72.8333); [15] : 索引路径: (length = 2, path = 0 - 15); 帧=(118.333 218.667;59.1429 72.8333); [16] : 索引路径: (length = 2, path = 0 - 16); 帧=(118.333 291.333;59.1429 72.8333); [17] : 索引路径: (length = 2, path = 0 - 17); 帧=(118.333 364.333;59.1429 72.8333); [18] : 索引路径: (length = 2, path = 0 - 18); 帧 = (177.333 0; 59.1429 72.8333); [19] : 索引路径: (length = 2, path = 0 - 19); 帧=(177.333 73;59.1429 72.8333); [20] : 索引路径: (length = 2, path = 0 - 20); 帧=(177.333 145.667;59.1429 72.8333); [21] : 索引路径: (length = 2, path = 0 - 21); 帧=(177.333 218.667;59.1429 72.8333); [22] : 索引路径: (length = 2, path = 0 - 22); 帧=(177.333 291.333;59.1429 72.8333); [23] : 索引路径: (length = 2, path = 0 - 23); 帧=(177.333 364.333;59.1429 72.8333); [24] : 索引路径: (length = 2, path = 0 - 24); 帧 = (236.667 0; 59.1429 72.8333); [25] : 索引路径: ( length = 2, path = 0 - 25); 帧 = (236.667 73; 59.1429 72.8333); [26] : 索引路径: (length = 2, path = 0 - 26); 帧=(236.667 145.667;59.1429 72.8333); [27] : 索引路径: (length = 2, path = 0 - 27); 帧=(236.667 218.667;59.1429 72.8333); [28] : 索引路径: (length = 2, path = 0 - 28); 帧 = (236.667 291.333; 59.1429 72.8333); [29] : 索引路径: (length = 2, path = 0 - 29); 帧=(236.667 364.333;59.1429 72.8333); [30] : 索引路径: (length = 2, path = 0 - 30); 帧 = (295.667 0; 59.1429 72.8333); [31] : 索引路径: (length = 2, path = 0 - 31); 帧=(295.667 73;59.1429 72.8333); [32] : 索引路径: (length = 2, path = 0 - 32); 帧=(295.667 145.667;59.1429 72.8333); [33] : 索引路径: (length = 2, path = 0 - 33); 帧=(295.667 218.667;59.1429 72.8333); [34] : 索引路径: (length = 2, path = 0 - 34); 帧=(295.667 291.333;59.1429 72.8333); [35] : 索引路径: (length = 2, path = 0 - 35); 帧=(295.667 364.333;59.1429 72.8333); [36] : 索引路径: (length = 2, path = 0 - 36); 帧 = (355 0; 59.1429 72.8333); [37] : 索引路径: (length = 2, path = 0 - 37); 帧 = (355 73; 59.1429 72.8333); [38] : 索引路径: (length = 2, path = 0 - 38); 帧 = (355 145.667; 59.1429 72.8333); [39] : 索引路径: (length = 2, path = 0 - 39); 帧 = (355 218.667; 59.1429 72.8333); [40] : 索引路径: (length = 2, path = 0 - 40); 帧 = (355 291.333; 59.1429 72.8333); [41] : 索引路径: (length = 2, path = 0 - 41); 帧 = (355 364.333; 59.1429 72.8333); [42] : 索引路径: (length = 2, path = 1 - 0); 帧 = (414 0; 59.1429 72.8333); [43] : 索引路径: ( length = 2, path = 1 - 1); 帧 = (414 73; 59.1429 72.8333); [44] : 索引路径: (length = 2, path = 1 - 2); 帧=(414 145.667;59.1429 72.8333); [45] : 索引路径: (length = 2, path = 1 - 3); 帧 = (414 218.667; 59.1429 72.8333); [46] : 索引路径: (length = 2, path = 1 - 4); 帧 = (414 291.333; 59.1429 72.8333); [47] : 索引路径: (length = 2, path = 1 - 5); 帧 = (414 364.333; 59.1429 72.8333); [48] : 索引路径: (length = 2, path = 1 - 6); 帧 = (473 0; 59.1429 72.8333); [49] : 索引路径: (length = 2, path = 1 - 7); 帧 = (473 73; 59.1429 72.8333); [50] : 索引路径: (length = 2, path = 1 - 8); 帧=(473 145.667;59.1429 72.8333); [51] : 索引路径: (length = 2, path = 1 - 9); 帧 = (473 218.667; 59.1429 72.8333); [52] : 索引路径: (length = 2, path = 1 - 10); 帧=(473 291.333;59.1429 72.8333); [53] : 索引路径: (length = 2, path = 1 - 11); 帧=(473 364.333;59.1429 72.8333); [54] : 索引路径: (length = 2, path = 1 - 12); 帧 = (532.333 0; 59.1429 72.8333); [55] : 索引路径: (length = 2, path = 1 - 13); 帧=(532.333 73;59.1429 72.8333); [56] : 索引路径: (length = 2, path = 1 - 14); 帧=(532.333 145.667;59.1429 72.8333); [57] : 索引路径: (length = 2, path = 1 - 15); 帧=(532.333 218.667;59.1429 72.8333); [58] : 索引路径: (length = 2, path = 1 - 16); 帧=(532.333 291.333;59.1429 72.8333); [59] : 索引路径: (length = 2, path = 1 - 17); 帧=(532.333 364.333;59.1429 72.8333); [60] : 索引路径: (length = 2, path = 1 - 18); 帧 = (591.333 0; 59.1429 72.8333); [61] : 索引路径: (length = 2, path = 1 - 19); 帧=(591.333 73;59.1429 72.8333); [62] : 索引路径: (length = 2, path = 1 - 20); 帧=(591.333 145.667;59.1429 72.8333); [63] : 索引路径: (length = 2, path = 1 - 21); 帧=(591.333 218.667;59.1429 72.8333); [64] : 索引路径: (length = 2, path = 1 - 22); 帧=(591.333 291.333;59.1429 72.8333); [65] : 索引路径: (length = 2, path = 1 - 23); 帧=(591.333 364.333;59.1429 72.8333); [66] : 索引路径: (length = 2, path = 1 - 24); 帧 = (650.667 0; 59.1429 72.8333); [67] : 索引路径: (length = 2, path = 1 - 25); 帧=(650.667 73;59.1429 72.8333); [68] : 索引路径: (length = 2, path = 1 - 26); 帧 = (650.667 145.667; 59.1429 72.8333); [69] : 索引路径: (length = 2, path = 1 - 27); 帧=(650.667 218.667;59.1429 72.8333); [70] : 索引路径: (length = 2, path = 1 - 28); 帧=(650.667 291.333;59.1429 72.8333); [71] : 索引路径: (length = 2, path = 1 - 29); 帧=(650.667 364.333;59.1429 72.8333); [72] : 索引路径: (length = 2, path = 1 - 30); 帧 = (709.667 0; 59.1429 72.8333); [73] : 索引路径: (length = 2, path = 1 - 31); 帧 = (709.667 73; 59.1429 72.8333); [74] : 索引路径: (length = 2, path = 1 - 32); 帧=(709.667 145.667;59.1429 72.8333); [75] : 索引路径: (length = 2, path = 1 - 33); 帧=(709.667 218.667;59.1429 72.8333); [76] : 索引路径: (length = 2, path = 1 - 34); 帧=(709.667 291.333;59.1429 72.8333); [77] : 索引路径: (length = 2, path = 1 - 35); 帧 = (709.667 364.333; 59.1429 72.8333);

我可以看到该函数要求索引路径:0-00-41 的第 0 部分是正确的。但是,它只要求索引路径1-01-35 用于其他部分。它缺少其他 6 个索引路径。当用户将丢失的单元格滚动到视图中时,它只会显示其他 6 条路径。它没有返回所有索引是否有原因?这甚至是检测矩形细胞的正确方法吗?

【问题讨论】:

您的布局是否具有itemSizeesrimatedItemSize 的值? @Paulw11 itemSize 和estimatedItemSize 设置正确。但是正如您提到的“布局”,您让我在那里仔细检查了我的代码。问题出在我的翻转代码中。通常 UICollectionView 垂直排列项目,但我有一些代码在那里水平排列。它有一些问题。谢谢大佬提醒。我会在一天左右删除这个问题,以免造成混乱。 或者你可以回答你自己的问题。如果您有问题,那么其他人可能也有类似的问题,您可以为他们指出正确的方向 我已经更新了你的问题。 与往常一样,这将与框架连接 - 也许您在正确布置框架之前询问框架,或者堆栈视图会导致其中的滚动视图出现问题。 【参考方案1】:

您的JTAppleCalendarFlowLayout 存在根本缺陷。

UICollectionView 在内部依赖 layoutAttributesForElementsInRect() 来了解要从 layoutAttributesForItemAtIndexPath 请求哪些单元格。 由于您将 rect 传递给 super 而不进行修改,因此它无法正确理解哪些单元格应该可见。

为了更好地演示该问题,我制作了保持同步的集合视图的第二个副本,但在底部禁用了您的 applyLayoutAttributes 修改。我还为出现太晚的单元格着色。

您必须了解的部分是 在顶部 collectionView 中出现“太晚”的单元格在原始/未修改的布局中尚不可见。集合视图仅在认为很快就会需要它们时才添加它们。它没有意识到你实际上已经让它们可见了。

那么......你应该如何解决它?

由于您的布局非常简单,我建议您不要打扰UICollectionViewFlowLayout,因为它并非旨在以这种方式处理“页面”。只需继承 UICollectionViewLayout,并通过自定义计算自己实现 layoutAttributesForElementsInRect 和 layoutAttributesForItemAtIndexPath。

或者,如果您真的打算使用 UICollectionViewFlowLayout,您将需要修改您传递给 super 的矩形,以便在原始布局下,矩形将包含所有将在修改后的布局下可见。

【讨论】:

感谢您的出色解释。连我不懂编程的朋友都懂!我会将其标记为正确。不过我有一个问题,我试图像你建议的那样从 UICollectionViewLayout 子类化,但这对我来说似乎很困难......你认为代码更改会很棒吗? 由于您的单元格是简单的矩形,并且每一行/列的大小都是固定的,我认为数学计算会很容易处理。至少这是一次很好的学习经历:) 我已经完成了 func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) 函数的数学运算。我不确定的是如何从func layoutAttributesForElementsInRect(rect: CGRect) 函数中获取 indexPaths,因为(如果我是 UICollectionViewLayout 的子类),我将无法调用super.layoutAttributesForElementsInRect(rect),但感谢您的回答。 对。您只需回答“哪些单元格应该可见?”这个问题。我怀疑这个问题可以通过基于矩形和每​​月行数的一些相当简单的计算来回答。【参考方案2】:

如果有人想看到我对 JTBandes 的出色响应所做的实现,他们可以找到答案 here on github。布局是为一个 ios calendarView 项目完成的。

日历视图有 7 列,可配置的行数为 1、2、3 或 6。 上面提到的问题发生在日历处于水平模式时。

我根据 JTBandes 的回答采用的解决方案是从 UICollectionViewLayout 继承而不是 UICollectionViewFlowLayout。所有代码都可以在存储库的this file 中找到。您还可以通过查看 this link 上的 Cocoapod 来对整个项目进行采样。谢谢大家。

【讨论】:

以上是关于使用 UICollectionViewFlowLayout / UICollectionViewLayout 子类,不会在正确的时间调用 CellForItemAtIndexPath的主要内容,如果未能解决你的问题,请参考以下文章

第一篇 用于测试使用

在使用加载数据流步骤的猪中,使用(使用 PigStorage)和不使用它有啥区别?

今目标使用教程 今目标任务使用篇

Qt静态编译时使用OpenSSL有三种方式(不使用,动态使用,静态使用,默认是动态使用)

MySQL db 在按日期排序时使用“使用位置;使用临时;使用文件排序”

使用“使用严格”作为“使用强”的备份