You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用CompositionalLayout调用UIMenu后滚动Item短暂消失问题求助

问题解决: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 03:30:53