插入多个自定义补充视图时的流布局动画故障

时间:2017-01-24 05:40:49

标签: ios swift animation uicollectionview uicollectionviewlayout

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

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

这是一个示范:

Animation showing two items and two supplementary views being inserted into a collection view with a simple flow layout.

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

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

尝试解决方案

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

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

  • 为这些方法提供初始和最终布局属性 - 调用super似乎会返回正确的帧,因此不清楚可以进行哪些调整(如果有的话)到初始或最终布局属性来解决问题。但请看下面提到的第三个好奇心。

  • 布局无效 -it似乎完全没有区别是在performBatchUpdates()之前,期间或之后,或者在全部或部分手动无效布局所有

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

调试时注意到好奇心

  1. 视觉故障特定于补充视图,插入项目不会出现,即使两者的索引路径和框架相同也是如此。

  2. 调试视图层次结构显示,虽然插入的补充视图的帧是正确的,但某些现有补充视图的帧不正确。尽管布局对象为这些索引路径上的现有视图返回的帧看起来是正确的,但事实如此。

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

  4. 由我的Swift代码创建的IndexPath实例的内存地址与UICollectionView和UICollectionViewFlowLayout内部创建的NSIndexPath实例截然不同。前者总是不同(如预期的那样),而后者在发射之间看起来是相同的(例如[0,0],[0,1]和[0,2]总是在0xc000000000000016,{{1 }和0xc000000000200016)。在Swift中,IndexPath桥接到NSIndexPath,但是the former is a value type whereas the latter is a reference type。 NSIndexPath也uses tagged pointers。很可能两者没有完全桥接和/或集合视图类内部依赖NSIndexPath行为以某种方式引起索引路径混淆。我不知道如何测试或更进一步。

  5. 类似问题

    以下问题可能有关,但没有一个答案对我有用:

    问题也可能与Open Radar上的this bug report有关。该报告附带的示例项目有点过于复杂,无法使用。

    Apple技术支持

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

      

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

         

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

         

    ...

         

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

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

2 个答案:

答案 0 :(得分:2)

以下是CustomCollectionViewController文件的输出 indexPath项目行适用于cellForItemAt indexPath行补充适用于viewForSupplementaryElementOfKind

enter image description here

正如您所看到的,cellForItemAt已正确返回indexpath.item 但在viewForSupplementaryElementOfKind中没有返回预期的答案。如果我能尽快给你更新,我现在还没有理由。

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

答案 1 :(得分:2)

我不确定我是否对初始问题有正确的解决方案,但我遇到的问题与@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)
    }
}

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