具有NSDiffableDataSource的组合UICollectionView在重新加载时跳转



我的UICollectionView有一个组合布局。这是用于创建布局的代码。

func createLayout() -> UICollectionViewLayout {
let layout = UICollectionViewCompositionalLayout { [weak self] section, _ -> NSCollectionLayoutSection? in
guard 
let self = self,
let sections = self.viewModel?.sections,
let sectionData = sections[safe: section] else { return nil }
switch sectionData {
case .firstSection:
return self.createFirstSectionSection()
case .secondSection:
return self.createSecondSection()
case .buttons(_, let topSpacing):
return self.createButtonsSection(topSpacing: topSpacing)
}
}
let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(108))
let header = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerSize,
elementKind: "header",
alignment: .top)
let config = UICollectionViewCompositionalLayoutConfiguration()
config.boundarySupplementaryItems = [header]
config.scrollDirection = .vertical
config.interSectionSpacing = 0
layout.configuration = config
return layout
}
func createFirstSection() -> NSCollectionLayoutSection {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(144))
let item = NSCollectionLayoutItem(layoutSize: itemSize, supplementaryItems: [borderItem])
let group = NSCollectionLayoutGroup.vertical(layoutSize: itemSize, subitems: [item])
group.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 60, bottom: 0, trailing: 20)
let layoutSection = NSCollectionLayoutSection(group: group)
return layoutSection
}
func createSecondSection() -> NSCollectionLayoutSection {
let borderItemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let borderItem  = NSCollectionLayoutSupplementaryItem(layoutSize: borderItemSize, elementKind: "item-border-view", containerAnchor: NSCollectionLayoutAnchor(edges: .top))
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(58))
let item = NSCollectionLayoutItem(layoutSize: itemSize, supplementaryItems: [borderItem])
let group = NSCollectionLayoutGroup.vertical(layoutSize: itemSize, subitems: [item])
group.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: hasCheckboxes ? 20 : 60, bottom: 0, trailing: 20)
let layoutSection = NSCollectionLayoutSection(group: group)
return layoutSection
}
func createButtonsSection(topSpacing: CGFloat) -> NSCollectionLayoutSection {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(41))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let group = NSCollectionLayoutGroup.vertical(layoutSize: NSCollectionLayoutSize(widthDimension: itemSize.widthDimension, heightDimension: itemSize.heightDimension), subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.contentInsets = NSDirectionalEdgeInsets(top: topSpacing, leading: 60, bottom: 0, trailing: 20)
return section
}

我的模型是这样的:

enum Section {
case firstSection(items: [FirstSectionItem])
case secondSection(items: [SecondSectionItem])
case buttons(cellViewModel: ButtonsCellViewModel, topSpacing: CGFloat)
var items: [AnyHashable] {
switch self {
case .firstSection(let firstSectionItems):
return firstSectionItems
case .quotes(let secondSectionItems):
return secondSectionItems
case .buttons(let cellViewModel, _):
return [cellViewModel]
}
}
}
// MARK: - Hashable
extension Section: Hashable {
static func == (lhs: Section, rhs: Section) -> Bool {
switch (lhs, rhs) {
case (.firstSection(let leftItems), .firstSection(let rightItems)):
return leftItems == rightItems
case (.secondSection(let leftItems), .secondSection(let rightItems)):
return leftItems == rightItems
case (.buttons(let leftCellViewModel, let leftTopSpacing), .buttons(let rightCellViewModel, let rightTopSpacing)):
return true
default:
return false
}
}
func hash(into hasher: inout Hasher) {
switch self {
case .firstSection(let items):
hasher.combine(items)
case .secondSection(let items):
hasher.combine(items)
case .buttons(let cellViewModel, let topSpacing):
hasher.combine("Same") // I use this to make sure that there is no difference in the buttons section. What I try to accomplish is that the buttons section (section at the bottom) does not animate out of screen to reload it's UI.
}
}
}

数据模型要复杂得多,但为了解决这个问题,我删除了一些我认为与此无关的内容,这些内容只会造成混乱。

使用DiffableDataSource重新加载collectionView如下所示:

func refreshUI() {
guard let viewModel = viewModel else { return }
let newDataSource = WorkApprovalDataSource(sections: viewModel.sections)
var snapshot = NSDiffableDataSourceSnapshot<APIWorkApprovalSection, AnyHashable>()
newDataSource.sections.forEach {
snapshot.appendSections([$0])
snapshot.appendItems($0.items, toSection: $0)
}
dataSource?.apply(snapshot, animatingDifferences: true)
}

重点是,我想要屏幕上的3个部分:

  • 第一部分:一些行/项在下面
  • 第二部分:与第一部分类似,但行/项是可选的
  • 第三部分:按钮部分。此部分始终存在。它至少有一个按钮。此节始终包含一个单元格:该单元格包含一个带按钮的水平堆栈视图。但是,正如我所说,至少总有一个按钮。通过选中/取消选中第2部分中的行/项,按钮部分中有一个额外的按钮。当没有选择行时-->没有额外的按钮。选中额外行时,按钮的标题会根据所选/选中的行数而更改:-->quot;发送(5(";例如当只检查了4行时,该标题需要更改为";发送(4(";。如果未选择任何行,则应隐藏此按钮

从一开始,我就在重新加载部分时遇到了麻烦。它上下跳跃。当检查第2节中的行时,取消按钮部分不可见,因为第2节的项目列表太大,例如,第一次检查/选择行时,它会跳转。之后,如果按钮部分仍然不在屏幕上,则选择和取消选择行没有问题,也不会发生跳转。

但是:当我滚动到底部,使按钮部分可见,然后我选择一行时,集合视图会滚动一点,使按钮看不见。当我再次滚动看到的按钮时,单元格中的数据看起来很好,所以重新加载发生了";正确";。我想要的是按钮部分不会滚动到屏幕外以重新加载UI。我已经通过使Hashable协议始终哈希相同的文本来处理这一问题,所以没有区别,对吧?按钮标题的更改及其可见性,我通过按钮的cellViewModel进行处理。所以这非常好。但是按钮一直滚动到看不见的地方重新加载。我不知道是什么原因造成的。

我真的需要装饰物品的构图布局,所以我不能放弃这个。

提前感谢您的浏览,并可能发布一些建议/修复。

我终于想通了。您必须将垂直滚动项目的布局组的维度设置为horizontal,而不是vertical。我确实读过几十篇教程,其中没有提到这一点,甚至在苹果的文档中也没有提到。我在这个愚蠢的滚动上浪费了好几天,如果不是几周的话。所以结论:DiffableDataSources工作得很好,是组合布局配置错误。

因此,与其对垂直滚动列表这样做:

let group = vertical(layoutSize: itemSize, subitems: [item])

你必须这样做:

let group = NSCollectionLayoutGroup.horizontal(layoutSize: itemSize, subitems: [item])

最新更新