iOS 自定义 UICollectionView 拼图布局 + 布局切换动画实践

🧩 iOS 自定义 UICollectionView 拼图布局 + 布局切换动画实践

本文只讲两件事: 👉 如何自定义 UICollectionView 布局 👉 如何优雅地切换布局并处理动画问题


✨ 一、为什么要自定义布局?

系统的 UICollectionViewFlowLayout 有一个明显限制:

👉 只能做规则网格(grid)布局

但很多场景需要:

  • 拼图布局(不规则)
  • 瀑布流变种
  • 卡片混排

例如👇

css 复制代码
┌───────────────┐
│       A       │
├───────┬───────┤
│   B   │   C   │
└───────┴───────┘

👉 这种布局 FlowLayout 是做不了的


🧠 二、自定义布局核心原理

自定义布局本质只做三件事:

1️⃣ 计算每个 cell 的 frame

objectivec 复制代码
UICollectionViewLayoutAttributes

2️⃣ 返回可见区域的 attributes

swift 复制代码
override func layoutAttributesForElements(in rect: CGRect)

3️⃣ 告诉 collectionView 内容尺寸

swift 复制代码
override var collectionViewContentSize: CGSize

🔥 三、实现一个拼图布局(MosaicLayout)

核心代码

swift 复制代码
class MosaicLayout: UICollectionViewFlowLayout {
​
    var layoutAttributes: [UICollectionViewLayoutAttributes] = []
    var contentSize: CGSize = .zero
​
    override func prepare() {
        super.prepare()
​
        guard let collectionView = collectionView else { return }
​
        layoutAttributes.removeAll()
​
        let count = collectionView.numberOfItems(inSection: 0)
        guard count > 0 else { return }
​
        let width = collectionView.bounds.width
        let height = width
​
        let frames: [CGRect] = [
            CGRect(x: 0, y: 0, width: 1, height: 0.6),
            CGRect(x: 0, y: 0.6, width: 0.5, height: 0.4),
            CGRect(x: 0.5, y: 0.6, width: 0.5, height: 0.4)
        ]
​
        for (index, relativeFrame) in frames.enumerated() {
​
            let frame = CGRect(
                x: relativeFrame.origin.x * width,
                y: relativeFrame.origin.y * height,
                width: relativeFrame.width * width,
                height: relativeFrame.height * height
            )
​
            let attr = UICollectionViewLayoutAttributes(forCellWith: IndexPath(item: index, section: 0))
            attr.frame = frame
            layoutAttributes.append(attr)
        }
​
        contentSize = CGSize(width: width, height: height)
    }
​
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        return layoutAttributes.filter { $0.frame.intersects(rect) }
    }
​
    override var collectionViewContentSize: CGSize {
        return contentSize
    }
}

🎬 五、布局切换动画

php 复制代码
collectionView.setCollectionViewLayout(newLayout, animated: true)

⚠️ 六、崩溃问题

swift 复制代码
The invalidation context is not an instance of UICollectionViewFlowLayoutInvalidationContext

解决方案

php 复制代码
collectionView.setCollectionViewLayout(newLayout, animated: false)
collectionView.reloadData()

🎯 七、总结

👉 自定义布局 = 自己控制 frame 👉 动画关键 = 避免同时更新数据源


如果你觉得有用,欢迎点赞 👍

相关推荐
门思科技15 分钟前
LoRaWAN 网络排障的五层漏斗:从"上不到云"到"数值不对",每一层该用什么工具证明
前端
计算机魔术师16 分钟前
Cursor 官方插件商城现恶意 Claude Code 扩展,约 11 小时影响最多 5000 名开发者
前端
掘金者阿豪25 分钟前
MongoDB迁移到金仓数据库:商品属性继续灵活存,订单报表交给 SQL
前端·后端
栉甜1 小时前
Vue2学习内容(上)
前端·javascript·vue.js·学习
梦曦i1 小时前
v0.2.0 架构升级:class + reactive 重塑状态管理
前端·uni-app x
布丁爱干饭1 小时前
寄给午后
前端
java1234_小锋1 小时前
【技术专题】CSS3 网页技术 - CSS3 布局进阶
前端·css·css3
zhippt_10142 小时前
只剩截图没有源文件?图转PPT把图片还原成可编辑幻灯片
前端·人工智能·powerpoint
shmily麻瓜小菜鸡2 小时前
TypeScript 知识点补充(第三部分:工程化与高级类型)
前端·javascript·typescript
10岁的博客3 小时前
Chrome 扩展安全攻防实录:从权限模型到真实漏洞复盘
前端·chrome·安全