【问题标题】:Flow layout animation glitch when inserting multiple custom supplementary views插入多个自定义补充视图时流布局动画故障
【发布时间】:2017-06-08 19:32:45
【问题描述】:

当使用UICollectionViewFlowLayout 的子类在具有现有补充视图的集合视图中插入多个自定义补充视图时,集合视图似乎会创建故障动画或无法正确处理插入。

插入一个补充视图的行为与预期相同,但一次插入两个补充视图会导致明显的视觉故障,并且一次插入多个补充视图(例如 4+)会使故障恶化。 (iOS 10.2、iOS 9.3、Swift 3、Xcode 8.2.1)

这是一个演示:

您可以使用this sample project 重现该问题。在这个简化的示例中,每个项目都有一个补充视图,并且在每次批量更新期间插入两个项目(具有两个补充视图)。在我的实际项目中,我的补充视图比项目少,并且我利用了其他流布局功能。

我最好的猜测是某些东西导致集合视图混淆了与现有/插入的补充视图相对应的索引路径或布局属性。如果这被证明是 Apple 课程中的一个错误,我将非常感谢您提供最优雅的解决方法。

尝试的解决方案

我已尝试并仔细检查了以下各项:

  • 实现indexPathsToInsertForSupplementaryView(ofKind:)——据我所知,这很简单,而且我的实现返回了正确的索引路径。始终插入视图。对于补充页眉和页脚视图,UICollectionViewFlowLayout 似乎返回了一个排序后的索引路径数组,但排序数组对我来说没有任何区别。

  • 提供初始和最终布局属性——为这些方法调用 super 似乎返回了正确的帧,因此不清楚可以对初始或最终布局属性来解决问题。但是请参阅下面提到的第三个好奇心。

  • 使布局无效——无论是在performBatchUpdates() 之前、期间或之后手动使布局全部或部分无效,还是完全无效,似乎都没有区别。

  • 自定义索引路径——尽管据我所知,补充元素的索引路径不需要与项目索引路径相对应,并且尽管有相反的文档,但 UICollectionViewFlowLayout 会崩溃(通过为不存在的索引路径请求布局属性)除非同类的补充元素从 0 开始按顺序编号。我假设这是集合视图和/或布局对象在插入元素时能够计算新索引路径的方式或删除(即通过增加或减少索引路径的item 属性)。所以构建任意索引路径不是一种选择。

调试时发现的好奇心

  1. 视觉故障是补充视图特有的,插入的项目不会发生,即使索引路径和帧对于两者来说都是相同的。

  2. 调试视图层次结构发现,虽然插入的补充视图的框架是正确的,但一些现有补充视图的框架是不正确的。尽管布局对象为这些索引路径上的现有视图返回的帧似乎是正确的,但情况仍然如此。

  3. 当插入多个补充视图时,集合视图对初始和最终布局属性的调用似乎不平衡。也就是说,对于同一个索引路径,初始布局属性被多次请求(当然,每次都返回相同的属性),对最终布局属性的请求较少。此外,对于一些现有的补充视图,根本不会请求初始和最终布局属性。我觉得这很可疑。这让我相信集合视图在某种程度上混淆了更新前后的补充视图和/或索引路径。

  4. 我的 Swift 代码创建的 IndexPath 实例的内存地址与 UICollectionView 和 UICollectionViewFlowLayout 在内部创建的 NSIndexPath 实例的内存地址截然不同。前者总是不同的(如预期的那样),而后者在启动之间似乎是相同的(例如 [0, 0]、[0, 1] 和 [0, 2] 总是在 0xc0000000000000160xc000000000200016 和 @ 987654341@)。在 Swift 中,IndexPath 桥接到 NSIndexPath,但 the former is a value type whereas the latter is a reference type。 NSIndexPath 也是uses tagged pointers。两者之间的桥接不完善和/或集合视图类在内部以某种方式依赖于 NSIndexPath 行为的可能性很小,这会导致此处明显的索引路径混淆。我不知道如何对此进行测试或进一步研究。

类似问题

以下问题可能是相关的,尽管没有一个答案对我有用:

这个问题也可能与 Open Radar 上的this bug report 有关。不过,该报告附带的示例项目有点过于复杂,没有多大用处。

Apple 技术支持

发布此问题后,我向 Apple 提交了技术支持事件。苹果开发者支持回复如下:

我们的工程师已审核您的请求,并确定最好将其作为错误报告处理。

请使用错误报告工具https://developer.apple.com/bug-reporting/ 提交有关此问题的完整错误报告。

...

我们花了一些时间调查解决方法的可能性,但不幸的是空手而归。请跟进您的错误。如果我们将来发现某种解决方法的可能性,我们将伸出援手。抱歉,我没有更好的消息。

如果您遇到此问题,请复制rdar://30510010

【问题讨论】:

  • 看起来像是 UICollectionView 错误的一部分。
  • 尝试使用 Bolts 库中的块依赖项(承诺实现)将一个插入链接到下一个插入。这应该避免同时插入故障,并且看起来更自然。
  • 您找到解决方法了吗?我也在为同样的事情苦苦挣扎,还时不时断言全局索引失败

标签: ios swift animation uicollectionview uicollectionviewlayout


【解决方案1】:

我不确定我是否对最初的问题有正确的解决方案,但我遇到了与@agent_stack 的答案相同的问题。

如果您有一个自定义集合视图布局(UICollectionViewLayout 的子类或 UICollectionViewFlowLayout 的子类)并且您添加了自己的补充视图,那么您必须重写一个附加方法:indexPathsToInsertForSupplementaryView(ofKind elementKind: String)

每当您将单元格或部分添加到集合视图时,集合视图都会调用此方法。实现此方法使您的布局对象有机会添加新的补充视图以补充添加内容。

我做了自己的 CollectionViewFlowLayout,只有在组合添加了这些方法之后,所有的东西都应该是动画了!

    override func prepare(forCollectionViewUpdates updateItems: [UICollectionViewUpdateItem]) {
    super.prepare(forCollectionViewUpdates: updateItems)

    // Prepare update by storing index path to insert
    // ────────────────────────────────────────────────────────────

    // IMPORTANT: Item vs. Section Update
    //
    // An instance of UICollectionViewUpdateItem can represent either an item or a section update,
    // and the only way to tell which is that update items which represent section updates contain
    // index paths with a valid section index and NSNotFound for the item index, while update items
    // that represent item updates contain index paths with valid item indexes.
    //
    // This appears to be completely undocumented by Apple, but it is absolutely consistent behavior.

    // https://www.raizlabs.com/blog/2013/10/animating_items_uicollectionview/

    var indexPaths = [IndexPath]()

    for updateItem in updateItems {

        switch updateItem.updateAction {

        case .insert:

            // If it is a section update then convert NSNotFound to zero.
            // In our case a section update always has only the first item.

            if var indexPath = updateItem.indexPathAfterUpdate {
                if indexPath.item == NSNotFound { indexPath.item = 0 }
                indexPaths.append(indexPath)
            }

        default:
            break
        }
    }

    indexPathsToInsert = indexPaths
}

override func indexPathsToInsertForSupplementaryView(ofKind elementKind: String) -> [IndexPath] {

    // Extra add supplementary index paths
    // ────────────────────────────────────────────────────────────

    // The collection view calls this method whenever you add cells or sections to the collection view.
    // Implementing this method gives your layout object an opportunity to add new supplementary views
    // to complement the additions.

    // http://stackoverflow.com/a/38289843/7441991

    switch elementKind {

    case UICollectionElementKindDateSeparator:
        return indexPathsToInsert

    case UICollectionElementKindAvatar:
        return indexPathsToInsert

    default:
        // If it is not a custom supplementary view, return attributes from flow layout
        return super.indexPathsToInsertForSupplementaryView(ofKind: elementKind)
    }
}

如果您有任何问题,请随时与我联系! :)

【讨论】:

    【解决方案2】:

    以下是CustomCollectionViewController 文件的输出indexPath item Row 用于cellForItemAt indexPathRow Supplementary 用于viewForSupplementaryElementOfKind

    如您所见,cellForItemAt 正确返回了indexpath.item 但在viewForSupplementaryElementOfKind 中没有返回预期的答案。我到现在还没弄明白原因,如果我知道了,我会尽快更新你。

    因此,为了重新解决,您可以通过在其中添加新数据来处理数组并重新加载 collectionView 而不是 indexPath。

    【讨论】:

    • 感谢您调查有关索引路径的困惑。不幸的是,reloadData() 不提供插入动画。
    • @jamesk 是的,你是对的,重新加载没有动画,但它可以解决这个错误。正如我在 3 单元插入补充中观察到的那样,0,1,2 已正确插入,但顺序混乱,但在 UI 中它以正确的形式出现。在插入第三个单元格后,viewForSupplementaryElement 被执行并在索引 4 处添加一个补充,如上图所示。昨天没找到原因。如果我插入 4 行,索引 6 也会发生同样的情况。我仍在搜索。如果我有理由,我会毫不犹豫地更新你。希望反过来也一样。 :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多