使用CompositionalLayout调用UIMenu后滚动Item短暂消失问题求助
问题描述
使用CompositionalLayout构建三个Section后,触发UIMenu(上下文菜单)后滚动CollectionView,出现单元格短暂消失的现象。
原因分析
这类问题通常源于以下几点:
- 上下文菜单交互后,CollectionView的布局状态与复用机制出现临时不一致,尤其是在带有正交滚动的Section中,CompositionalLayout的布局计算和菜单交互的状态同步存在延迟。
- 单元格复用未正确重置视觉状态,导致旧状态残留引发显示异常。
- 布局创建逻辑封装在CollectionView的懒加载闭包内部,可能导致布局对象的重复定义或异常初始化。
解决方案
1. 重构布局创建逻辑
将布局相关的函数移到ViewController类的顶层,避免在懒加载闭包内重复定义,确保布局对象初始化稳定:
class ViewController: UIViewController { let collectionView: UICollectionView = { let view = UICollectionView(frame: .zero, collectionViewLayout: createCompositionalLayout()) view.register(CollectionViewCell.self, forCellWithReuseIdentifier: CollectionViewCell.id) view.register(CollectionViewHeader.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: CollectionViewHeader.id) view.backgroundColor = .systemGroupedBackground return view }() // 移到类顶层的布局创建函数 private static func createCompositionalLayout() -> UICollectionViewCompositionalLayout { return UICollectionViewCompositionalLayout { (sectionNumber, env) -> NSCollectionLayoutSection? in switch sectionNumber { case 0: return firstSection() case 1: return secondSection() case 2: return thirdSection() default: return firstSection() } } } private static func firstSection() -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0)) let item = NSCollectionLayoutItem(layoutSize: itemSize) item.contentInsets = .init(top: 0, leading: 0, bottom: 11, trailing: 11) let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.47), heightDimension: .absolute(135)) let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 1) let section = NSCollectionLayoutSection(group: group) section.orthogonalScrollingBehavior = .continuous section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 16, bottom: 20, trailing: 0) section.boundarySupplementaryItems = [ NSCollectionLayoutBoundarySupplementaryItem(layoutSize: .init(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(44)), elementKind: UICollectionView.elementKindSectionHeader, alignment: .topLeading) ] return section } private static func secondSection() -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalWidth(1)) let item = NSCollectionLayoutItem(layoutSize: itemSize) item.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 4, trailing: 4) let groupHeight = NSCollectionLayoutDimension.fractionalWidth(0.7) let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.92), heightDimension: groupHeight) let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitem: item, count: 3) let section = NSCollectionLayoutSection(group: group) section.orthogonalScrollingBehavior = .groupPaging section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 16, bottom: 20, trailing: 0) return section } private static func thirdSection() -> NSCollectionLayoutSection { let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalWidth(0.5)) let item = NSCollectionLayoutItem(layoutSize: itemSize) item.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 2, bottom: 2, trailing: 2) let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalWidth(0.5)) let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 2) group.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2) let section = NSCollectionLayoutSection(group: group) section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 16, bottom: 20, trailing: 16) return section } override func viewDidLoad() { super.viewDidLoad() view.addSubview(collectionView) collectionView.delegate = self collectionView.dataSource = self collectionView.snp.makeConstraints { $0.edges.equalToSuperview() } } }
2. 确保单元格复用状态重置
在cellForItemAt方法中,明确重置单元格的所有视觉状态,避免复用残留:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CollectionViewCell.id, for: indexPath) as? CollectionViewCell else { return UICollectionViewCell() } // 重置单元格状态,比如背景色、内容等,示例: cell.backgroundColor = .white // 其他自定义内容重置逻辑 return cell }
3. 上下文菜单操作后同步布局状态
在UIMenu的Action中,添加布局刷新逻辑,确保交互后CollectionView状态同步:
func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemsAt indexPaths: [IndexPath], point: CGPoint) -> UIContextMenuConfiguration? { return UIContextMenuConfiguration(actionProvider: { suggestedActions in if indexPaths.count == 0 { return UIMenu(children: [ UIAction(title: "New Folder") { _ in // 操作后刷新布局 collectionView.collectionViewLayout.invalidateLayout() } ]) } else if indexPaths.count == 1 { return UIMenu(children: [ UIAction(title: "Copy") { _ in collectionView.reloadItems(at: indexPaths) }, UIAction(title: "Delete", attributes: .destructive) { _ in // 执行删除逻辑后刷新 collectionView.reloadItems(at: indexPaths) } ]) } else { return UIMenu(children: [ UIAction(title: "New Folder With Selection") { _ in collectionView.collectionViewLayout.invalidateLayout() } ]) } }) }
补充说明
如果上述方案未解决问题,可以尝试关闭CollectionView的预加载(collectionView.prefetchingEnabled = false),检查是否是预加载导致的状态冲突;另外,确保iOS版本适配,部分旧版本系统的CompositionalLayout存在已知的上下文菜单交互bug,升级系统或使用适配补丁也能解决。
内容的提问来源于stack exchange,提问作者idhun90
相关产品推荐
相关产品推荐

