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

UICollectionView复杂网格布局实现难题求助

UICollectionView复杂网格布局实现方案

问题说明

  • 目标布局:组合式网格,包含1个大尺寸单元格与多个小尺寸单元格,大单元格占据整行(竖屏)或半行(横屏)空间,小单元格两两并排填充剩余区域
  • 当前实现:仅通过sizeForItemAt设置固定尺寸,只能得到所有单元格大小一致的简单网格
  • 尝试方案及痛点:
    • UICollectionViewCompositionalLayout:设备旋转时需硬编码组内子项数量,灵活性不足;滚动后存在布局计算不完整的问题
    • 自定义UICollectionViewLayout子类:性能表现极差
    • 复合单元格(内置4个子单元格):无法适配动态布局需求,灵活性缺失

解决方案

一、优化UICollectionViewCompositionalLayout实现

针对旋转灵活性和滚动布局问题,可通过动态计算组结构和布局失效触发解决:

1. 动态生成布局结构

根据容器尺寸自动调整组内元素排列,无需固定子项数量:

func createDynamicLayout() -> UICollectionViewLayout {
    let layout = UICollectionViewCompositionalLayout { _, layoutEnv in
        let containerSize = layoutEnv.container.contentSize
        let isLandscape = containerSize.width > containerSize.height
        
        // 定义大单元格尺寸
        let largeItemSize = NSCollectionLayoutSize(
            widthDimension: .fractionalWidth(isLandscape ? 0.5 : 1.0),
            heightDimension: .fractionalHeight(1.0)
        )
        let largeItem = NSCollectionLayoutItem(layoutSize: largeItemSize)
        largeItem.contentInsets = .init(top: 2, leading: 2, bottom: 2, trailing: 2)
        
        // 定义小单元格尺寸
        let smallItemSize = NSCollectionLayoutSize(
            widthDimension: .fractionalWidth(0.5),
            heightDimension: .fractionalHeight(0.5)
        )
        let smallItem = NSCollectionLayoutItem(layoutSize: smallItemSize)
        smallItem.contentInsets = .init(top: 2, leading: 2, bottom: 2, trailing: 2)
        
        // 小单元格横向组(2个一组)
        let smallItemGroup = NSCollectionLayoutGroup.horizontal(
            layoutSize: NSCollectionLayoutSize(
                widthDimension: .fractionalWidth(isLandscape ? 0.5 : 1.0),
                heightDimension: .fractionalHeight(0.5)
            ),
            subitems: [smallItem, smallItem]
        )
        
        // 主组合组,横竖屏自动切换排列方向
        let mainGroup: NSCollectionLayoutGroup
        if isLandscape {
            mainGroup = NSCollectionLayoutGroup.horizontal(
                layoutSize: .init(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(0.5)),
                subitems: [largeItem, smallItemGroup]
            )
        } else {
            mainGroup = NSCollectionLayoutGroup.vertical(
                layoutSize: .init(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0)),
                subitems: [largeItem, smallItemGroup]
            )
        }
        
        let section = NSCollectionLayoutSection(group: mainGroup)
        section.contentInsets = .init(top: 2, leading: 2, bottom: 2, trailing: 2)
        return section
    }
    return layout
}

2. 修复滚动布局计算问题

在设备旋转或容器尺寸变化时,主动触发布局失效更新:

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)
    coordinator.animate(alongsideTransition: { _ in
        self.collectionView.collectionViewLayout.invalidateLayout()
    })
}

二、基于UICollectionViewFlowLayout的轻量自定义扩展

如果CompositionalLayout仍有问题,可基于FlowLayout扩展,仅调整布局属性,性能优于完全自定义Layout:

class CustomGridFlowLayout: UICollectionViewFlowLayout {
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        guard let baseAttributes = super.layoutAttributesForElements(in: rect),
              let collectionView = collectionView else { return nil }
        
        var updatedAttributes = [UICollectionViewLayoutAttributes]()
        var currentYOffset = sectionInset.top
        let containerWidth = collectionView.bounds.width
        let isLandscape = containerWidth > collectionView.bounds.height
        
        for attr in baseAttributes {
            guard attr.representedElementCategory == .cell else {
                updatedAttributes.append(attr)
                continue
            }
            
            let indexPath = attr.indexPath
            let cellSpacing = minimumInteritemSpacing
            let edgeInsets = sectionInset
            
            if indexPath.item % 3 == 0 {
                // 大单元格布局
                let cellWidth = isLandscape ? (containerWidth/2 - edgeInsets.left - edgeInsets.right) : (containerWidth - edgeInsets.left - edgeInsets.right)
                let cellHeight = isLandscape ? cellWidth : cellWidth
                attr.frame = CGRect(
                    x: edgeInsets.left,
                    y: currentYOffset,
                    width: cellWidth,
                    height: cellHeight
                )
                currentYOffset += cellHeight + minimumLineSpacing
            } else {
                // 小单元格布局(2个一排)
                let smallCellWidth = isLandscape ? ((containerWidth/2 - edgeInsets.left - edgeInsets.right) - cellSpacing)/2 : ((containerWidth - edgeInsets.left - edgeInsets.right) - cellSpacing)/2
                let smallCellHeight = smallCellWidth
                
                if indexPath.item % 3 == 1 {
                    let xPos = isLandscape ? (containerWidth/2 + edgeInsets.left) : edgeInsets.left
                    attr.frame = CGRect(x: xPos, y: currentYOffset, width: smallCellWidth, height: smallCellHeight)
                } else {
                    let xPos = isLandscape ? (containerWidth/2 + edgeInsets.left + smallCellWidth + cellSpacing) : (edgeInsets.left + smallCellWidth + cellSpacing)
                    attr.frame = CGRect(x: xPos, y: currentYOffset, width: smallCellWidth, height: smallCellHeight)
                    currentYOffset += smallCellHeight + minimumLineSpacing
                }
            }
            updatedAttributes.append(attr)
        }
        return updatedAttributes
    }
    
    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
        // 尺寸变化时自动更新布局
        return true
    }
}

三、通用注意事项

  • 确保单元格contentView添加正确的约束,避免尺寸错乱
  • 使用CompositionalLayout时,尽量减少嵌套组层级,降低布局计算复杂度
  • 性能测试可借助Instruments的Core Animation工具定位卡顿点,针对性优化

内容的提问来源于stack exchange,提问作者Maxim

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 09:45:00