这份就是自定义
UICollectionViewLayout的全部核心 API 头文件定义 ; 你继承UICollectionViewLayout写瀑布流、卡片、圆环、圆形布局、翻页效果、复杂不规则网格,全部重写这里面的方法。
swift
import CoreGraphics
import Foundation
import UIKit
extension UICollectionView {
/// 表头视图 标记字符串
@available(iOS 6.0, *)
public class let elementKindSectionHeader: String
/// 表尾视图 标记字符串
@available(iOS 6.0, *)
public class let elementKindSectionFooter: String
/// 滚动方向
public enum ScrollDirection : Int, @unchecked Sendable {
case vertical = 0 // 垂直滚动(上下滑),默认
case horizontal = 1 // 水平滚动(左右滑)
}
/// 布局元素类型枚举
public enum ElementCategory : UInt, @unchecked Sendable {
case cell = 0 // 正常内容Cell单元格
case supplementaryView = 1 // 追加视图:Header / Footer
case decorationView = 2 // 装饰视图:背景、分割线,不绑定数据源
}
}
// MARK: - 布局属性:决定每一个Cell/Header/Footer/装饰View的位置、大小、形变、透明度
/// 场景:自定义布局的核心数据模型。
/// 每一个视图都对应一份LayoutAttributes,布局计算就是生成一堆该对象。
@available(iOS 6.0, *)
@MainActor open class UICollectionViewLayoutAttributes : NSObject, NSCopying, UIDynamicItem {
/// 视图的frame(原点+宽高)
open var frame: CGRect
/// 中心点坐标
open var center: CGPoint
/// 尺寸大小
open var size: CGSize
/// 3D形变 CATransform3D,可做透视、旋转3D效果
open var transform3D: CATransform3D
/// iOS7+ bounds,相对于自身坐标系
@available(iOS 7.0, *)
open var bounds: CGRect
/// iOS7+ 2D仿射变换:缩放、旋转、平移
@available(iOS 7.0, *)
open var transform: CGAffineTransform
/// 透明度 0~1
open var alpha: CGFloat
/// 层级,数值越大越在上层,可以实现视图重叠效果
open var zIndex: Int
/// 是否隐藏
open var isHidden: Bool
/// 当前元素对应的索引位置
open var indexPath: IndexPath
/// 只读:当前元素类型 Cell / supplementary / decoration
open var representedElementCategory: UICollectionView.ElementCategory { get }
/// 只读:追加视图标记字符串,Header/Footer;Cell返回nil
open var representedElementKind: String? { get }
/// 创建Cell单元格布局属性
public convenience init(forCellWith indexPath: IndexPath)
/// 创建Header/Footer追加视图布局属性
public convenience init(forSupplementaryViewOfKind elementKind: String, with indexPath: IndexPath)
/// 创建装饰视图布局属性(背景、分割线等)
public convenience init(forDecorationViewOfKind decorationViewKind: String, with indexPath: IndexPath)
}
// MARK: - 布局失效上下文:实现**局部刷新布局**,优化性能,不要全量重绘
/// 场景1:滚动时只刷新可视区域Item;
/// 场景2:某几个Cell高度变化,只刷新这几个,不用invalidateLayout()全部刷新;
/// 场景3:自定义动画、交互式拖拽移动Item。
@available(iOS 7.0, *)
@MainActor open class UICollectionViewLayoutInvalidationContext : NSObject {
/// 是否全部布局失效,true=所有布局全部重新计算
open var invalidateEverything: Bool { get }
/// 是否数据源数量发生变化(增删Cell),需要重新计算
open var invalidateDataSourceCounts: Bool { get }
/// iOS8+ 标记指定若干个Item需要重新布局
@available(iOS 8.0, *)
open func invalidateItems(at indexPaths: [IndexPath])
/// iOS8+ 刷新指定Header/Footer追加视图
@available(iOS 8.0, *)
open func invalidateSupplementaryElements(ofKind elementKind: String, at indexPaths: [IndexPath])
/// iOS8+ 刷新指定装饰视图
@available(iOS 8.0, *)
open func invalidateDecorationElements(ofKind elementKind: String, at indexPaths: [IndexPath])
/// 获取本次需要刷新的cell索引数组
@available(iOS 8.0, *)
open var invalidatedItemIndexPaths: [IndexPath]? { get }
/// 获取本次刷新的追加视图字典 [kind字符串 : 索引数组]
@available(iOS 8.0, *)
open var invalidatedSupplementaryIndexPaths: [String : [IndexPath]]? { get }
/// 获取本次刷新的装饰视图
@available(iOS 8.0, *)
open var invalidatedDecorationIndexPaths: [String : [IndexPath]]? { get }
/// iOS8+ 滚动偏移量调整;滚动过程布局变化,修正contentOffset防止跳动
@available(iOS 8.0, *)
open var contentOffsetAdjustment: CGPoint
/// iOS8+ 内容总大小调整
@available(iOS 8.0, *)
open var contentSizeAdjustment: CGSize
/// iOS9+ 交互式移动Item:移动前索引数组
@available(iOS 9.0, *)
open var previousIndexPathsForInteractivelyMovingItems: [IndexPath]? { get }
/// iOS9+ 交互式移动Item:目标位置索引数组
@available(iOS 9.0, *)
open var targetIndexPathsForInteractivelyMovingItems: [IndexPath]? { get }
/// iOS9+ 拖拽移动手势的触点坐标
@available(iOS 9.0, *)
open var interactiveMovementTarget: CGPoint { get }
}
// MARK: - 布局基类 UICollectionViewLayout 主入口
/// 场景:所有自定义布局的父类;瀑布流、卡片轮播、环形布局、不规则网格都继承它
@available(iOS 6.0, *)
@MainActor open class UICollectionViewLayout : NSObject, NSCoding {
public init()
public init?(coder: NSCoder)
/// 弱引用绑定的collectionView,只读
open var collectionView: UICollectionView? { get }
/// 使整个布局全部失效,触发重新布局计算;性能差,会刷新全部
open func invalidateLayout()
/// iOS7+ 带上下文的布局失效,推荐局部刷新;高性能,精准刷新部分Item
@available(iOS 7.0, *)
open func invalidateLayout(with context: UICollectionViewLayoutInvalidationContext)
/// 注册装饰视图Class,背景/分割线等
open func register(_ viewClass: AnyClass?, forDecorationViewOfKind elementKind: String)
/// 通过Xib注册装饰视图
open func register(_ nib: UINib?, forDecorationViewOfKind elementKind: String)
}
// MARK: - 【核心布局计算回调】最常重写的一批方法
extension UICollectionViewLayout {
/// 返回自定义的LayoutAttributes子类类型;如果你继承UICollectionViewLayoutAttributes扩展字段就重写
open class var layoutAttributesClass: AnyClass { get }
/// iOS7+ 返回自定义的失效上下文类型;自定义局部刷新逻辑重写
@available(iOS 7.0, *)
open class var invalidationContextClass: AnyClass { get }
/// ⭐布局计算第一步!所有布局计算在这里写。
/// 场景:一次性预计算全部Cell、Header、Footer的frame;瀑布流高度缓存就在这里。
open func prepare()
/// ⭐核心回调:返回rect可视范围内所有元素的布局属性。CollectionView滚动时高频调用。
open func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]?
/// 获取单个Cell对应的布局属性
open func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes?
/// 获取Header/Footer追加视图布局属性
open func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes?
/// 获取装饰视图布局属性
open func layoutAttributesForDecorationView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes?
/// ⭐边界变化(滚动、缩放)时是否需要刷新布局;
/// 场景:卡片轮播,滚动实时形变,返回true;普通瀑布流返回false。
open func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool
/// iOS7+ 滚动边界变化时,返回自定义的失效上下文对象
@available(iOS 7.0, *)
open func invalidationContext(forBoundsChange newBounds: CGRect) -> UICollectionViewLayoutInvalidationContext
/// iOS8+ Cell自适应宽高(Self‑Sizing),系统预估尺寸和最终实际尺寸不一样时是否刷新布局
@available(iOS 8.0, *)
open func shouldInvalidateLayout(forPreferredLayoutAttributes preferredAttributes: UICollectionViewLayoutAttributes, withOriginalAttributes originalAttributes: UICollectionViewLayoutAttributes) -> Bool
/// iOS8+ 自适应尺寸布局失效上下文
@available(iOS 8.0, *)
open func invalidationContext(forPreferredLayoutAttributes preferredAttributes: UICollectionViewLayoutAttributes, withOriginalAttributes originalAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutInvalidationContext
/// ⭐非常高频!松手滚动减速时,修正最终停留位置;实现分页、卡片居中停靠效果必重写!
open func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint
/// iOS7+ 无速度参数的目标偏移回调
@available(iOS 7.0, *)
open func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint
/// ⭐只读属性,返回CollectionView整体内容总大小;类似UIScrollView contentSize,必须重写!
open var collectionViewContentSize: CGSize { get }
/// 获取布局方向 LTR / RTL
open var developmentLayoutDirection: UIUserInterfaceLayoutDirection { get }
/// 是否在从右向左语言下,水平布局自动翻转
open var flipsHorizontallyInOppositeLayoutDirection: Bool { get }
}
// MARK: - 【增删动画过渡回调】Cell插入/删除动画、布局切换动画
/// 场景:自定义删除渐隐、飞入出场动画;两个Layout互相切换转场动画
extension UICollectionViewLayout {
/// 开始执行批量增删更新动画前回调
open func prepare(forCollectionViewUpdates updateItems: [UICollectionViewUpdateItem])
/// 批量增删动画全部完成后回调
open func finalizeCollectionViewUpdates()
/// bounds即将发生变化动画之前
open func prepare(forAnimatedBoundsChange oldBounds: CGRect)
/// bounds动画结束
open func finalizeAnimatedBoundsChange()
/// iOS7+ 切换到新布局之前(转场动画)
@available(iOS 7.0, *)
open func prepareForTransition(to newLayout: UICollectionViewLayout)
/// iOS7+ 从旧布局切换出来
@available(iOS 7.0, *)
open func prepareForTransition(from oldLayout: UICollectionViewLayout)
/// iOS7+ 布局切换动画完成
@available(iOS 7.0, *)
open func finalizeLayoutTransition()
/// 即将出现的Item,入场动画起始位置属性;实现飞入效果
open func initialLayoutAttributesForAppearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes?
/// 即将消失的Item,退场动画结束位置属性;实现消失动画
open func finalLayoutAttributesForDisappearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes?
/// Header/Footer入场动画初始属性
open func initialLayoutAttributesForAppearingSupplementaryElement(ofKind elementKind: String, at elementIndexPath: IndexPath) -> UICollectionViewLayoutAttributes?
/// Header/Footer退场结束属性
open func finalLayoutAttributesForDisappearingSupplementaryElement(ofKind elementKind: String, at elementIndexPath: IndexPath) -> UICollectionViewLayoutAttributes?
/// 装饰视图入场动画
open func initialLayoutAttributesForAppearingDecorationElement(ofKind elementKind: String, at decorationIndexPath: IndexPath) -> UICollectionViewLayoutAttributes?
/// 装饰视图退场动画
open func finalLayoutAttributesForDisappearingDecorationElement(ofKind elementKind: String, at decorationIndexPath: IndexPath) -> UICollectionViewLayoutAttributes?
/// iOS7+ 返回需要删除的追加视图索引数组
@available(iOS 7.0, *)
open func indexPathsToDeleteForSupplementaryView(ofKind elementKind: String) -> [IndexPath]
/// iOS7+ 返回需要删除的装饰视图索引数组
@available(iOS 7.0, *)
open func indexPathsToDeleteForDecorationView(ofKind elementKind: String) -> [IndexPath]
/// iOS7+ 返回需要插入追加视图索引数组
@available(iOS 7.0, *)
open func indexPathsToInsertForSupplementaryView(ofKind elementKind: String) -> [IndexPath]
/// iOS7+ 返回需要插入装饰视图索引数组
@available(iOS 7.0, *)
open func indexPathsToInsertForDecorationView(ofKind elementKind: String) -> [IndexPath]
}
// MARK: - 【iOS9+ 交互式拖拽移动Item】长按拖拽排序功能
extension UICollectionViewLayout {
/// iOS9+ 根据拖拽手势坐标,算出目标移动到哪一个IndexPath
@available(iOS 9.0, *)
open func targetIndexPath(forInteractivelyMovingItem previousIndexPath: IndexPath, withPosition position: CGPoint) -> IndexPath
/// iOS9+ 拖拽过程中,被拖拽Item实时布局属性(跟随手指移动)
@available(iOS 9.0, *)
open func layoutAttributesForInteractivelyMovingItem(at indexPath: IndexPath, withTargetPosition position: CGPoint) -> UICollectionViewLayoutAttributes
/// iOS9+ 拖拽移动开始,生成失效上下文
@available(iOS 9.0, *)
open func invalidationContext(forInteractivelyMovingItems targetIndexPaths: [IndexPath], withTargetPosition targetPosition: CGPoint, previousIndexPaths: [IndexPath], previousPosition: CGPoint) -> UICollectionViewLayoutInvalidationContext
/// iOS9+ 拖拽手势结束;movementCancelled=true代表拖拽取消,不换位
@available(iOS 9.0, *)
open func invalidationContextForEndingInteractiveMovementOfItems(toFinalIndexPaths indexPaths: [IndexPath], previousIndexPaths: [IndexPath], movementCancelled: Bool) -> UICollectionViewLayoutInvalidationContext
}
自定义布局开发最简必重写清单(80% 场景只需要这 4 个)
表格
| 方法 | 作用 | 使用场景 |
|---|---|---|
prepare() |
预计算所有 item 位置,缓存 frame 数组 | 瀑布流、环形布局、不规则网格 |
collectionViewContentSize |
返回整体滚动内容宽高 | 所有自定义布局必须实现 |
layoutAttributesForElements(in rect:) |
返回可视区域 item 布局 | 滚动实时渲染 |
targetContentOffset(...) |
松手停靠偏移修正 | 卡片轮播、分页居中效果 |
高级场景对应 API
-
滚动时 Cell 缩放、倾斜动画
shouldInvalidateLayout(forBoundsChange:)返回 true
-
高性能局部刷新,避免全量重绘卡顿
invalidateLayout(with:)+UICollectionViewLayoutInvalidationContext
-
Cell 增删自定义出场入场动画
initialLayoutAttributesForAppearingItem/finalLayoutAttributesForDisappearingItem
-
长按拖拽排序
- iOS9+ 交互式移动 Item 一套方法
-
布局切换动画 (A 布局→B 布局带动画)
prepareForTransition系列转场回调
-
背景装饰视图、网格分割线
- DecorationView 装饰视图 + register 注册