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

UICollectionView切换Compositional Layout时单元格消失问题

UICollectionView Compositional Layout切换正交滚动时单元格消失问题

问题表现

iOS开发中切换UICollectionView的Compositional Layout配置以开启或关闭正交滚动功能时,会出现单元格消失异常:首次切换布局可正常生效,切回原布局时单元格直接消失。
异常效果如下:
Cells disappearing

核心配置代码

两种布局仅正交滚动配置存在差异:

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.31 23:36:37