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
相关产品推荐
相关产品推荐

