【问题标题】:Appending sections in NSCollectionViewDiffableDataSource在 NSCollectionViewDiffableDataSource 中附加部分
【发布时间】:2020-03-06 14:28:13
【问题描述】:

我正在尝试使用NSCollectionViewDiffableDataSourceNSCollectionViewCompositionalLayout 创建具有动态部分数量的NSCollectionView

集合视图用于显示搜索结果,部分的数量取决于找到的结果的类型和数量。每个部分使用不同的布局显示其内容类型。

数据源声明为NSCollectionViewDiffableDataSource<String, SearchResult>,其中SearchResult 是使用UUID() 实现Hashable 的类。结果为零的部分不是空的,而是在集合视图中不存在。

当显示我的视图控制器时,我会清除现有的搜索结果:

func clearSearchResults(animate: Bool) {
    let snapshot = NSDiffableDataSourceSnapshot<String, SearchResult>()
    dataSource.apply(snapshot, animatingDifferences: animate)
}

执行搜索时,我尝试为找到的每种类型的结果在集合视图中添加一个部分:

// Code that performs the search
var snapshot = NSDiffableDataSourceSnapshot<String, SearchResult>()

// If I append more than one section an exception is thrown in apply():
// snapshot.appendSections([ViewController.trackSection, ViewController.albumSection])

snapshot.appendSections([ViewController.trackSection])
snapshot.appendItems(tracks, toSection: ViewController.trackSection)

// This also causes an exception in apply():
// snapshot.appendSections([ViewController.albumSection])
// snapshot.appendItems(albums, toSection: ViewController.albumSection)

dataSource.apply(snapshot, animatingDifferences: true)

堆栈跟踪是:

2019-11-10 13:34:29.883728-0600 DiffableTest[64931:1820050] [General] An uncaught exception was raised
2019-11-10 13:34:29.883813-0600 DiffableTest[64931:1820050] [General] -[NSCollectionView insertSections:] Section index 1 out of bounds
2019-11-10 13:34:29.883937-0600 DiffableTest[64931:1820050] [General] (
    0   CoreFoundation                      0x00007fff33a98f53 __exceptionPreprocess + 250
    1   libobjc.A.dylib                     0x00007fff69b5e835 objc_exception_throw + 48
    2   CoreFoundation                      0x00007fff33a98da9 +[NSException raise:format:] + 189
    3   UIFoundation                        0x00007fff6469fd5b -[_NSCollectionViewCore insertSections:] + 267
    4   UIFoundation                        0x00007fff6465dd5e -[_NSDiffableDataSourceViewUpdater _performNSCollectionViewInsertUpdate:] + 222
    5   UIFoundation                        0x00007fff6465db33 -[_NSDiffableDataSourceViewUpdater _performViewUpdates:] + 594
    6   AppKit                              0x00007fff3156d8e8 __58-[NSCollectionView performBatchUpdates:completionHandler:]_block_invoke + 21
    7   UIFoundation                        0x00007fff646aabe5 -[_NSCollectionViewCore _performBatchUpdates:completion:invalidationContext:tentativelyForReordering:animator:] + 323
    8   UIFoundation                        0x00007fff646aaa7f -[_NSCollectionViewCore _performBatchUpdates:completion:invalidationContext:tentativelyForReordering:] + 90
    9   UIFoundation                        0x00007fff646aaa02 -[_NSCollectionViewCore _performBatchUpdates:completion:invalidationContext:] + 74
    10  UIFoundation                        0x00007fff646aa957 -[_NSCollectionViewCore performBatchUpdates:completion:] + 53
    11  AppKit                              0x00007fff3156d7f4 -[NSCollectionView performBatchUpdates:completionHandler:] + 282
    12  UIFoundation                        0x00007fff6465d2ee -[_NSDiffableDataSourceViewUpdater _performUpdateWithCollectionViewUpdateItems:dataSourceSnapshotter:updateHandler:completion:] + 528
    13  UIFoundation                        0x00007fff646c72a2 -[__NSDiffableDataSource _commitNewDataSource:withViewUpdates:completion:] + 265
    14  UIFoundation                        0x00007fff646c1cb9 __66-[__NSDiffableDataSource applyDifferencesFromSnapshot:completion:]_block_invoke.259 + 190
    15  UIFoundation                        0x00007fff646c1fd2 __66-[__NSDiffableDataSource applyDifferencesFromSnapshot:completion:]_block_invoke.284 + 170
    16  libdispatch.dylib                   0x000000010039e78f _dispatch_client_callout + 8
    17  libdispatch.dylib                   0x00000001003af4cb _dispatch_lane_barrier_sync_invoke_and_complete + 135
    18  UIFoundation                        0x00007fff646c172d -[__NSDiffableDataSource applyDifferencesFromSnapshot:completion:] + 842
    19  UIFoundation                        0x00007fff64765417 +[_NSUIAnimator performWithAnimation:] + 90
    20  UIFoundation                        0x00007fff646c2939 -[__NSDiffableDataSource applyDifferencesFromSnapshot:animatingDifferences:completion:] + 158
    21  libswiftAppKit.dylib                0x00007fff6a2f6bb3 $s6AppKit34NSCollectionViewDiffableDataSourceC5apply_20animatingDifferences10completionyAA010NSDiffablefG8SnapshotVyxq_G_SbyycSgtF + 211
    22  DiffableTest                        0x0000000100005c73 $s12DiffableTest14ViewControllerC13performSearchyyyXlSgF + 3059

我是否以某种方式滥用了 API?

有关示例项目,请参阅 https://github.com/sbooth/DiffableTest

【问题讨论】:

  • 你有没有想过这个问题?您是使用纯 Swift 结构/类作为标识符,还是像示例代码中那样使用 NSObject 派生类?在 macOS 上使用纯 Swift 实体存在一些混淆,因为 NSCollectionViewDiffableDataSource 仅在 10.15.1 中引入(没有任何文档或发行说明)。使用基于引用的 API 对我有用,但我也使用 10.15.3 下的纯 Swift API 得到越界异常。
  • @kennyc 我从来没有像我想要/预期的那样工作。我正在使用纯 Swift。从我收集到的NSCollectionView 有附加部分和移动项目的错误。在基本上每个操作(步骤更新)之后调用apply() 并始终至少有一个部分,可以使事情正常进行。
  • 感谢您的跟进。我(再次)收到了一份 DTS 报告,以尝试更好地理解 NSCollectionView 和可区分的数据源。我可以使用基于 Reference 的 API 使其工作,这需要使用 NSObject 派生标识符,但尝试使用 Swift 类、结构或枚举总是会导致引发索引越界异常,即使我基本上使用来自 WWDC 的示例代码。

标签: swift macos cocoa nscollectionview


【解决方案1】:

我发现有时需要调用两次apply(_:animatingDifferences:),一次是在调用appendSection(_:) 和动画false 之后,一次是在调用appendItems(_:) 之后。

例如:

extension AnimalsViewController {
    enum Section {
        case main
    }

    private func makeDataSource() -> UITableViewDiffableDataSource<Section, String> {
        UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, name in
            let cell = tableView
                .dequeueReusableCell(withIdentifier: CellIdentifier.animalCell.rawValue,
                                     for: indexPath)

            cell.textLabel?.text = name
            return cell
        }
    }

    private func update() {
        activityIndicator.stopAnimating()
        var snapshot = NSDiffableDataSourceSnapshot<Section, String>()
        snapshot.appendSections([.main])
        dataSource.apply(snapshot, animatingDifferences: false)
        snapshot.appendItems(viewModel.animalNames)
        dataSource.apply(snapshot, animatingDifferences: true)
    }
}

【讨论】:

  • 这与 Apple DTS 建议的类似,但 iOS 和 macOS 上的集合视图有很大不同,所以如果 iOS 有同样的问题,那就太糟糕了。
【解决方案2】:

经过我的一些简短测试,当数据源使用动画应用快照时,似乎抛出了节索引越界异常。如果在修改任何部分时禁用动画,则似乎不会引发异常。 (至少在我的基本测试中没有。)

以下代码看来对我来说可以正常工作:

let outgoingSectionCount = dataSource.numberOfSections(in:collectionView)
let incomingSectionCount = snapshot.sectionIdentifiers.count

let shouldAnimate  = true
let canAnimate     = outgoingSectionCount == incomingSectionCount

dataSource.apply(snapshot, animatingDifferences:(shouldAnimate && canAnimate)

YMMV...

跟进

正如您在上面的评论中所回避的那样,Apple DTS 建议您在附加部分之后但在附加任何项目之前致电dataSource.apply(...)。成功插入部分后,您应该能够插入项目并再次致电dataSource.apply(...)

【讨论】:

    【解决方案3】:

    似乎与NSCollectionViewDiffableDataSource 相关的一些错误已在 macOS 11 中得到解决。在 macOS 10.15.* 中应用带有动画崩溃的快照似乎在 macOS 11 中的大多数情况下都有效。

    【讨论】:

      【解决方案4】:

      我自己也遇到了这个棘手的问题。唯一可靠的解决方法是在应用快照之前使布局无效。

      if snapshot.sectionIdentifiers.isEmpty {
          collectionView.collectionViewLayout.invalidateLayout()
      }
      
      dataSource.apply(snapshot, ...)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-11-01
        • 1970-01-01
        • 2013-06-06
        • 2014-07-16
        相关资源
        最近更新 更多