ios - 插入多個自定義補充視圖時的流佈局動畫故障




swift animation (2)

使用UICollectionViewFlowLayout的子類在具有現有補充視圖的集合視圖中插入多個自定義補充視圖時,集合視圖似乎會創建出色的動畫或者無法正確處理插入。

插入一個補充視圖的行為符合預期,但一次插入兩個補充視圖會導致明顯的視覺故障,並且一次插入許多補充視圖(例如4+)會使故障惡化。 (iOS 10.2,iOS 9.3,Swift 3,Xcode 8.2.1)

這是一個演示:

您可以使用此示例項目重現該問題。 在該簡化示例中,每個項目有一個補充視圖,並且在每個批次更新期間插入兩個項目(具有兩個補充視圖)。 在我的實際項目中,我的補充視圖少於項目,我使用其他流程佈局功能。

我最好的猜測是,某些東西導致集合視圖混淆了與現有/插入的補充視圖相對應的索引路徑或佈局屬性。 如果這是Apple課程中的一個錯誤,我將非常感謝您最優雅的解決方法。

試圖解決方案

我試過並仔細檢查以下各項:

  • 實現indexPathsToInsertForSupplementaryView(ofKind:)據我所知,這很簡單,我的實現返回正確的索引路徑。 始終插入視圖。 對於補充頁眉和頁腳視圖,UICollectionViewFlowLayout似乎返回索引路徑的排序數組,但排序數組對我沒有任何影響。

  • 提供初始和最終佈局屬性 - 為這些方法調用super似乎返回正確的幀,因此對於初始或最終佈局屬性可以進行哪些調整(如果有)以解決問題並不明顯。 但請看下面提到的第三個好奇心。

  • 使佈局無效 - 在performBatchUpdates()之前,期間或之後,或者根本不是全部或部分地手動使佈局無效,似乎沒有任何區別。

  • 自定義索引路徑 - 儘管補充元素的索引路徑不需要對應於項索引路徑,但據我所知,儘管有相反的文檔,但UICollectionViewFlowLayout將崩潰(通過請求不存在的索引路徑的佈局屬性),除非相同類型的補充元素從0開始按順序編號。我假設這是集合視圖和/或佈局對像在插入或刪除元素時(即通過遞增或遞減元素屬性)計算新索引路徑的方式。索引路徑)。 因此,構建任意索引路徑不是一種選擇。

調試時注意到好奇心

  1. 視覺故障特定於補充視圖,並且對於插入的項目不會發生,即使索引路徑和幀對於兩者都相同。

  2. 調試視圖層次結構表明,雖然插入的補充視圖的幀是正確的,但是一些現有補充視圖的幀不正確。 儘管佈局對象為這些索引路徑上的現有視圖返回的幀看起來是正確的,但事實仍是如此。

  3. 插入多個補充視圖時,集合視圖對初始和最終佈局屬性的調用似乎是不平衡的。 也就是說,對於相同的索引路徑,多次請求初始佈局屬性(當然,每次都返回相同的屬性),並且對最終佈局屬性的請求較少。 此外,對於一些現有的補充視圖,從不要求初始和最終佈局屬性。 我覺得這很可疑。 這讓我相信集合視圖在更新之前和之後會以某種方式混淆補充視圖和/或索引路徑。

  4. 我的Swift代碼創建的IndexPath實例的內存地址與UICollectionView和UICollectionViewFlowLayout內部創建的NSIndexPath實例截然不同。 前者總是不同(如預期的那樣),而後者在啟動之間看起來是相同的(例如[0,0],[0,1]和[0,2]總是在0xc0000000002000160xc000000000400016 )。 在Swift中,IndexPath橋接到NSIndexPath,但前者是值類型,而後者是引用類型 。 NSIndexPath還使用標記指針 。 很可能兩者都不完美地橋接和/或集合視圖類內部依賴NSIndexPath行為以某種方式引起索引路徑混淆。 我不知道如何測試或更進一步。

類似的問題

以下問題可能有關,但沒有一個答案對我有用:

  • 如何正確插入或刪除UICollectionView補充視圖

  • layoutAttributesForSupplementaryViewOfKind:atIndexPath:傳入不正確的indexPath

  • UICollectionView補充視圖不會為“in”或“out”設置動畫

  • 無法移動UICollectionView裝飾和補充視圖

該問題也可能與Open Radar上的這個錯誤報告有關。 該報告附帶的示例項目有點過於復雜而無法使用。

Apple技術支持

發布此問題後,我向Apple提交了技術支持事件。 Apple Developer Support回復如下:

我們的工程師已經審核了您的請求,並確定最好將其作為錯誤報告處理。

請使用https://developer.apple.com/bug-reporting/的錯誤報告工具提交有關此問題的完整錯誤報告。

...

我們花了一些時間調查解決方法的可能性,不幸的是空手而歸。 請跟進你的錯誤。 如果我們將來發現某種解決方法的可能性,我們將與您聯繫。 對不起,我沒有更好的消息。

如果您遇到此問題,請複制rdar://30510010


下面是CustomCollectionViewController文件的輸出indexPath項目用於cellForItemAt indexPathRow Supplementary用於viewForSupplementaryElementOfKind

正如您所看到的, cellForItemAt正確地返回了indexpath.item但是在viewForSupplementaryElementOfKind中沒有返回預期的答案。 如果我能盡快給你更新,我現在還沒有理由。

因此,對於重新求解,您可以通過在其中添加新數據來處理數組並重新加載collectionView而不是indexPath。


我不確定我是否對初始問題有正確的解決方案,但我遇到的問題與@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://.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)
    }
}

如果您有任何疑問,請隨時與我聯繫! :)







uicollectionviewlayout