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

Swift实现UICollectionView表格式跨行列布局及旋转适配

问题描述

本人来自Android开发领域,正在开发原生iOS应用,需为展示组件实现一种表格式布局:单元格并非单纯按行排列,而是支持不同的行跨度和列跨度,设计布局大致为:

  • 左上角大单元格占2行2列
  • 右侧上下两个小单元格各占2行1列
  • 下方左右两个小单元格各占1行2列

目前已编写如下代码,但效果与预期相差甚远:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    let insets = collectionView.contentInset
    let collectionViewWidth = collectionView.frame.width - (insets.left + insets.right + 1)
    let collectionViewHeight = collectionView.frame.height - (insets.top + insets.bottom)

    switch indexPath.row {
    case 0:
        return CGSize(width: collectionViewWidth, height: collectionViewWidth)
    case 1:
        return CGSize(width: collectionViewWidth / 2, height: collectionViewWidth / 2)
    default:
        return CGSize(width: collectionViewWidth, height: collectionViewWidth)
    }
}

当前效果是单元格仅按单行流式排列,无法实现跨行跨列的表格布局。

此外该布局还需支持:

  • 屏幕90°旋转及垂直翻转的适配
  • 后续通过定时器逐个切换单元格内容

请问该需求是否可行?如何用Swift实现?


解决方案

这个需求完全可行,iOS中可以通过UICollectionViewCompositionalLayout(iOS 13+)快速实现这类带行列跨度的布局,比传统的UICollectionViewFlowLayout更灵活,也能轻松适配屏幕旋转。

1. 实现核心布局逻辑

首先定义每个单元格的行列跨度和尺寸,通过CompositionalLayout构建布局:

func createLayout() -> UICollectionViewLayout {
    // 基础网格单元:占1列1行
    let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5),
                                         heightDimension: .fractionalHeight(1.0))
    let baseItem = NSCollectionLayoutItem(layoutSize: itemSize)
    baseItem.contentInsets = NSDirectionalEdgeInsets(top: 1, leading: 1, bottom: 1, trailing: 1) // 单元格间距

    // 按分组构建整体布局
    let layout = UICollectionViewCompositionalLayout { sectionIndex, _ in
        switch sectionIndex {
        case 0:
            // 第一组:左上大单元格 + 右侧两个竖排小单元格
            let largeItemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                                      heightDimension: .fractionalHeight(1.0))
            let largeItem = NSCollectionLayoutItem(layoutSize: largeItemSize)
            
            // 右侧竖排小单元格组
            let smallVGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5),
                                                       heightDimension: .fractionalHeight(0.5))
            let smallVGroup = NSCollectionLayoutGroup.vertical(layoutSize: smallVGroupSize, subitems: [baseItem])
            
            // 第一组整体
            let mainGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                                      heightDimension: .fractionalHeight(0.5))
            let mainGroup = NSCollectionLayoutGroup.horizontal(layoutSize: mainGroupSize, subitems: [largeItem, smallVGroup])
            
            return NSCollectionLayoutSection(group: mainGroup)
        case 1:
            // 第二组:下方两个横排大单元格
            let bottomGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                                        heightDimension: .fractionalHeight(0.5))
            let bottomGroup = NSCollectionLayoutGroup.horizontal(layoutSize: bottomGroupSize, subitems: [baseItem, baseItem])
            
            return NSCollectionLayoutSection(group: bottomGroup)
        default:
            return NSCollectionLayoutSection(group: NSCollectionLayoutGroup.horizontal(layoutSize: itemSize, subitems: [baseItem]))
        }
    }
    return layout
}

初始化CollectionView时使用该布局:

let collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createLayout())
collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(collectionView)

2. 适配屏幕旋转

CompositionalLayout会自动根据视图尺寸调整布局,只需确保CollectionView跟随父视图尺寸变化:

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()
    collectionView.frame = view.bounds
}

同时在ViewController中设置支持的旋转方向:

override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
    return .all // 可根据需求调整为指定方向
}

3. 定时器切换单元格内容

通过Timer定时更新数据源并刷新指定单元格:

var timer: Timer?
var currentTileIndex = 0
var tileData: [TileModel] = [] // 自定义数据模型,存储单元格内容

func startTileSwitchTimer() {
    timer = Timer.scheduledTimer(withTimeInterval: 3.0, repeats: true) { [weak self] _ in
        guard let self = self else { return }
        // 更新对应索引的单元格数据
        self.tileData[self.currentTileIndex] = self.generateNewTileData()
        // 刷新单个单元格
        self.collectionView.reloadItems(at: [IndexPath(row: self.currentTileIndex, section: 0)])
        // 循环切换索引
        self.currentTileIndex = (self.currentTileIndex + 1) % self.tileData.count
    }
}

// 在页面显示/隐藏时启停定时器
override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    startTileSwitchTimer()
}

override func viewDidDisappear(_ animated: Bool) {
    super.viewDidDisappear(animated)
    timer?.invalidate()
    timer = nil
}

兼容iOS 12及以下方案

如果需要兼容低版本系统,可以自定义UICollectionViewLayout,通过重写layoutAttributesForElements(in:)和collectionViewContentSize方法,手动计算每个单元格的frame和跨度,但实现复杂度较高,推荐优先使用CompositionalLayout。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 05:23:12