可区分的集合视图如何在多个部分显示相同的项目?

How can a diffable collection view display the same item in multiple sections?

我的应用程序提供了一个类似于 Apple 的表情符号键盘,其中表情符号按类别显示。我使用类别是部分的集合视图来呈现它。如果最近插入了表情符号,它应该出现在 "Frequently Used" 类别以及它通常所在的类别中。

这对我尝试将集合视图转换为使用 UICollectionViewDiffableDataSource 来说是个问题,因为 NSDiffableDataSourceSnapshot 要求项目是唯一的。如果我做类似

的事情
let snapshot = NSDiffableDataSourceSnapshot<Section, Emoji>()
snapshot.appendItems([thumbsUpEmoji], toSection: .frequents)
snapshot.appendItems([thumbsUpEmoji], toSection: .smileys)
dataSource.apply(snapshot)

我收到类似

的警告

inserted identifier(s) already present; existing items will be moved into place for this current insertion. Please note this will impact performance if items are not unique when inserted.

而且表情符号只出现在一个部分,不会同时出现在两个部分。如何将项目插入多个部分?

我发现我可以通过将表情符号包装在将表情符号与其部分相关联的结构中来做到这一点:

struct CategorizedEmoji: Hashable {
    let emoji: Emoji
    let category: Section
}

我的数据源是 UICollectionViewDiffableDataSource<Section, CategorizedEmoji> 类型,快照是 NSDiffableDataSourceSnapshot<Section, CategorizedEmoji> 类型。构建快照时我做

let snapshot = NSDiffableDataSourceSnapshot<Section, CategorizedEmoji>()
snapshot.appendItems([CategorizedEmoji(emoji: thumbsUpEmoji, category: .frequents)], toSection: .frequents)
snapshot.appendItems([CategorizedEmoji(emoji: thumbsUpEmoji, category: .smileys)], toSection: .smileys)
dataSource.apply(snapshot)

有点冗长,但还算不错。

警告:我猜这个解决方案会阻止表情符号在部分之间移动(因为不同部分中的表情符号将由完全不同的项目表示)。我个人不需要处理这个问题,但我很高兴看到能解决这个问题的答案。

这是一个通用实现,因此您可以将其用于所有分区列表:

/// The following allows items to be used across different shard key-spaces, by combining a shard's hash values with the item's hash.
///
/// EXAMPLE: For sectioned UICollectionViews, simply use a section identifier as the sharding key to allow items to show up in multiple sections-
struct ShardedItem<TShardingKey, TItem> : Hashable
    where TShardingKey:Hashable, TItem:Hashable
{
    let shard:TShardingKey
    let item:TItem
}

然后像这样简单地使用它:

注意:使用 iOS 14 sectioned snapshots

var snapshot = NSDiffableDataSourceSnapshot<Section, ShardedItem<Section, Thing>>()
snapshot.appendSections( [.topThings, .allThings] )
self.dataSource.apply( snapshot, animatingDifferences: false )

let allThings:[Thing] = ...
let topThings:[Thing] = allThings.filter{ [=11=].isFeatured }
var topThingsSectionSnapshot = NSDiffableDataSourceSectionSnapshot<ShardedItem<Section, Thing>>()
topThingsSectionSnapshot.append( topThings.map { ShardedItem( shard: .topThings, item: [=11=] )})
self.dataSource.apply( topThingsSectionSnapshot, to: .topThings )

var allThingsSectionSnapshot = NSDiffableDataSourceSectionSnapshot<ShardedItem<MinorRegionsSection, MinorRegionListItem>>()
allThingsSectionSnapshot.append( allThings.map { ShardedItem( shard: .allThings, item: [=11=] )})
self.dataSource.apply( allThingsSectionSnapshot, to: .allThings )