iOS Swift如何用同一UICollectionView切换列表、网格、瀑布流布局
实现方案
完全可以用同一个UICollectionView实现三种布局切换,具体实现步骤如下:
1. 新增布局状态管理
在控制器的Properties代码块中新增布局类型枚举和当前布局状态变量:
// 定义三种布局类型 enum LayoutType { case list // 单行列表布局 case grid // 双列网格布局(原有默认布局) case staggered // 瀑布流布局 } // 当前布局状态,默认是双列网格 var currentLayout: LayoutType = .grid
2. 实现三种布局的构造方法
推荐用iOS13+支持的UICollectionViewCompositionalLayout实现列表、网格布局,瀑布流用通用自定义布局实现,新增布局构造方法:
func getCurrentLayout() -> UICollectionViewLayout { switch currentLayout { case .list: // 单行列表布局:宽度撑满父视图,高度可自定义或自适应 let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(260)) let item = NSCollectionLayoutItem(layoutSize: itemSize) let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(260)) let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) let section = NSCollectionLayoutSection(group: group) section.interGroupSpacing = lineSpacing section.contentInsets = .init(top: 10, leading: 10, bottom: 10, trailing: 10) return UICollectionViewCompositionalLayout(section: section) case .grid: // 原有双列网格布局:正方形item let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .fractionalHeight(1.0)) let item = NSCollectionLayoutItem(layoutSize: itemSize) item.contentInsets = .init(top: 0, leading: interitemSpacing/2, bottom: 0, trailing: interitemSpacing/2) let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalWidth(0.5)) let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) let section = NSCollectionLayoutSection(group: group) section.interGroupSpacing = lineSpacing section.contentInsets = .init(top: 10, leading: 10, bottom: 10, trailing: 10) return UICollectionViewCompositionalLayout(section: section) case .staggered: // 瀑布流布局:用通用自定义StaggeredGridLayout实现 let layout = StaggeredGridLayout() layout.numberOfColumns = 2 layout.cellPadding = interitemSpacing layout.delegate = self return layout } }
3. 实现瀑布流高度计算代理
瀑布流需要根据图片比例动态计算每个cell的高度,先声明瀑布流布局代理,再让控制器遵守协议:
// 瀑布流布局代理协议 protocol StaggeredGridLayoutDelegate: AnyObject { func collectionView(_ collectionView: UICollectionView, heightForItemAtIndexPath indexPath: IndexPath) -> CGFloat } // 控制器实现代理方法 extension HotViewController: StaggeredGridLayoutDelegate { func collectionView(_ collectionView: UICollectionView, heightForItemAtIndexPath indexPath: IndexPath) -> CGFloat { let photo = filterSwitch.isOn ? filteredPhotos[indexPath.row] : hotPhotos[indexPath.row] // 根据图片宽高比计算cell高度 let cellWidth = (view.bounds.width - 20 - interitemSpacing) / 2 let imageRatio = CGFloat(photo.height) / CGFloat(photo.width) return cellWidth * imageRatio + 20 // 20为cell内边距/其他控件预留高度 } }
4. 绑定按钮点击事件实现布局切换
给两个按钮绑定点击方法,切换布局时支持动画过渡:
// 按钮1点击:切换为单行列表布局 @IBAction func listBtnClicked(_ sender: UIButton) { currentLayout = .list collectionView.setCollectionViewLayout(getCurrentLayout(), animated: true) collectionView.reloadData() } // 按钮2点击:切换为瀑布流布局 @IBAction func staggeredBtnClicked(_ sender: UIButton) { currentLayout = .staggered collectionView.setCollectionViewLayout(getCurrentLayout(), animated: true) collectionView.reloadData() } // 如需切回双列网格,补充对应点击方法即可 @IBAction func gridBtnClicked(_ sender: UIButton) { currentLayout = .grid collectionView.setCollectionViewLayout(getCurrentLayout(), animated: true) collectionView.reloadData() }
5. 调整原有代码
- 删除原有
UICollectionViewDelegateFlowLayout相关的所有代理方法,改为用上述布局方法控制尺寸 - 在
viewDidLoad中初始化默认布局:
override func viewDidLoad() { super.viewDidLoad() collectionView.collectionViewLayout = getCurrentLayout() setupViewModel() }
注意事项
PhotoListCell需要做好自动布局适配,确保内部图片控件可以跟随cell尺寸自动缩放- 如果需要兼容iOS13以下版本,将列表、网格布局也改为自定义FlowLayout实现即可,逻辑完全一致
- 自定义
StaggeredGridLayout是通用成熟实现,可直接使用通用开源代码,无需从零开发
内容的提问来源于stack exchange,提问作者user16780334
相关产品推荐
相关产品推荐

