UICollectionView切换Compositional Layout时单元格消失问题
UICollectionView Compositional Layout切换正交滚动时单元格消失问题
问题表现
iOS开发中切换UICollectionView的Compositional Layout配置以开启或关闭正交滚动功能时,会出现单元格消失异常:首次切换布局可正常生效,切回原布局时单元格直接消失。
异常效果如下:
核心配置代码
两种布局仅正交滚动配置存在差异:
section.orthogonalScrollingBehavior = hasOrthogonalScroll ? .groupPagingCentered : .none
带动画切换布局的调用代码:
collectionView.setCollectionViewLayout(layout, animated: true)
可复现问题的完整代码
可直接复制到新项目ViewController文件中运行复现问题:
class ViewController: UIViewController, UICollectionViewDelegate { var collectionView: UICollectionView! override func viewDidLoad() { super.viewDidLoad() collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionLayout(hasOrthogonalScroll: false)) collectionView.delegate = self collectionView.frame = view.bounds collectionView.autoresizingMask = [.flexibleHeight, .flexibleWidth] view.addSubview(collectionView) setupDataSource() } private func collectionLayout(hasOrthogonalScroll: Bool) -> UICollectionViewLayout { let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0)) let item = NSCollectionLayoutItem(layoutSize: itemSize) let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalWidth(0.2)) let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) let section = NSCollectionLayoutSection(group: group) section.orthogonalScrollingBehavior = hasOrthogonalScroll ? .groupPagingCentered : .none return UICollectionViewCompositionalLayout(section: section) } enum Section { case main } private var dataSource: UICollectionViewDiffableDataSource<Section, Int>! private func setupDataSource() { let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, Int> { (cell, indexPath, identifier) in var configuration = cell.defaultContentConfiguration() configuration.text = identifier.description cell.contentConfiguration = configuration var backgroundConfiguration = cell.backgroundConfiguration backgroundConfiguration?.backgroundColor = .secondarySystemBackground backgroundConfiguration?.cornerRadius = 8 backgroundConfiguration?.backgroundInsets = .init(top: 4, leading: 4, bottom: 4, trailing: 4) cell.backgroundConfiguration = backgroundConfiguration } dataSource = UICollectionViewDiffableDataSource<Section, Int>(collectionView: collectionView) { collectionView, indexPath, identifier in collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: identifier) } var snapshot = NSDiffableDataSourceSnapshot<Section, Int>() snapshot.appendSections([.main]) snapshot.appendItems(Array(1...100)) dataSource.apply(snapshot, animatingDifferences: false) } var hasOrthogonalScroll = false func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { collectionView.deselectItem(at: indexPath, animated: true) hasOrthogonalScroll.toggle() let layout = collectionLayout(hasOrthogonalScroll: hasOrthogonalScroll) collectionView.setCollectionViewLayout(layout, animated: true) } }
问题原因
这是UIKit Compositional Layout的已知内部状态bug:当动态切换orthogonalScrollingBehavior属性(从非正交滚动切换到正交滚动,再切回非正交滚动)时,系统不会正确重置section对应的内部嵌套滚动容器,也不会在动画结束后重新计算可见单元格的布局属性,最终导致单元格的frame被错误计算为0、或者layer被标记为隐藏,表现为单元格消失。
修复方案
- 方案1:动画完成后强制刷新集合视图
利用setCollectionViewLayout的完成回调,在动画结束后调用reloadData()强制刷新所有单元格,修正布局状态:collectionView.setCollectionViewLayout(layout, animated: true) { [weak self] _ in guard let self = self else { return } self.collectionView.reloadData() // 若需要保持当前选中项的位置,可追加滚动逻辑 // self.collectionView.scrollToItem(at: indexPath, at: .centeredVertically, animated: false) } - 方案2:使用无动画方式切换布局
若业务场景不需要布局切换动画,直接将animated参数设为false,可完全规避动画流程中的状态计算错误:collectionView.setCollectionViewLayout(layout, animated: false) - 方案3:切换前主动失效布局
切换布局前先调用layoutIfNeeded()刷新当前布局状态,新布局应用前主动调用invalidateLayout()触发属性重算,也可降低问题出现概率。
内容的提问来源于stack exchange,提问作者Zaphod
相关产品推荐
相关产品推荐

