(iOS) UICollectionViewLayoutInvalidationContext性能优化 详细流程图 + 范例

本文深入解析UICollectionView的布局原理,包括UICollectionViewLayout的基本使用、UICollectionViewLayoutAttributes的作用及处理方式,以及如何通过invalidateLayout实现局部刷新。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

起步基础

  • UICollectionViewLayout 基本使用

  • UICollectionViewLayoutAttributes

Attributes赋值

这里泛指了以下两个主函数,就不在赘述两个功能,以及 UICollectionViewLayoutAttributes 需处理的变量。

class AutoSizingLayout: UICollectionViewLayout {

	override func prepare() {
		super.prepare()
	}
	
	override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
		return attributes
	}

}
复制代码

Without Invalidating

先来看看在 没有需要重新改变Attributes 下的流程(以下简称 配置流程):

data reload时,prepare计算一次,layoutAttributesForElements调用多次直到,系统已经有所有IndexPath的atrribute,就不会在调用这些functiom,直到collectionView reload。

With Invalidating

现在我们把失效的概念加进来

强制失效 UICollectionViewLayout.invalidateLayout()

invalidateLayout()可随时呼叫,他会将所有系统已取得的 Attribute 全部标记为 invalid 并舍弃。

准确的update时机并不是调用后,而是在下一次 layout 的 update Cycle里后,重新调用prepare. 堆栈如图:

https://developer.apple.com/documentation/uikit/uicollectionviewlayout/1617728-invalidatelayout

若有overrider此方法,必须call super.invalidateLayout()

条件失效 UICollectionViewLayout.shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool

https://developer.apple.com/documentation/uikit/uicollectionviewlayout/1617781-shouldinvalidatelayout

默认回传 false。

overrider后,可借由传入的 newBounds 判断是否需要 invalidLayout,若回传 true 则跟 InvalidateLayout()之后的流程(堆栈)相同。

例如内容下半部,需要不断更新Attribute

override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
	guard let collectionView = collectionView else { return false }
	if newBounds.maxY > contentSize.height / 2 {
   		return true
	}
	return false
}
复制代码

newBounds: CGRect

此bounds的触发时机,为collectionView可视范围改动时(contentOffset Change)

为了简化流程图,我们将固定一起出现的这几个步骤,划成一个:

UICollectionViewLayoutInvalidationContext

(以下简称 InvalidationContext) https://developer.apple.com/documentation/uikit/uicollectionviewlayoutinvalidationcontext

这的context跟出现在其他地方的Context上下文概念差不多,先借由一个function的参数,对此上下文进行设置,回传后再下一个function对设置的内容进行处理。

基于系统『原生』的 InvalidationContext 失效layout

这里再多覆写了两个函数

  • invalidationContext(forBoundsChange:)

    可以借由参数 bounds 对context 进行部分逻辑处理,也可在这做 『失效标记』

    https://developer.apple.com/documentation/uikit/uicollectionviewlayout/1617781-shouldinvalidatelayout

override func invalidationContext(forBoundsChange newBounds: CGRect) -> UICollectionViewLayoutInvalidationContext {
		let invalidationContext = super.invalidationContext(forBoundsChange: newBounds)
		return invalidationContext
}

复制代码
  • invalidateLayout(with:)

    根据上一部处理好的逻辑或 『失效标记』 做属性处理,必调用super.invalidateLayout(with: invalidationContext)

    override func invalidateLayout(with context: UICollectionViewLayoutInvalidationContext) {
    	//以context的资讯,做些update
    }
    复制代码

系统提供可用的失效标记,无任何标记将会重新进入『配置流程』

这些标记的功用,是在第一个function根据bounds作上标记,在第二个funciton中可以根据以下对应的变数取得当初的标记,做对应的『局部属性预处理』。

有标记可做 局部属性预处理 ,并会被重新询问 Attribute

重新询问 Attribute

例如:若滑超过 1/2 Y,使 row 17 失效。


override func invalidationContext(forBoundsChange newBounds: CGRect) -> UICollectionViewLayoutInvalidationContext {
	let invalidationContext = super.invalidationContext(forBoundsChange: newBounds)
	if newBounds.maxY > contentSize.height {
		invalidationContext.invalidateItems(at: [IndexPath(row: 17, section: 0)])
    }
    return invalidationContext
}
复制代码

覆写的此function就会被调用并询问 row17 的attribute

override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
    return row17's attribute
}
复制代码
局部属性预处理

局部属性预处理 其实就是为了上一步 『重新询问 Attribute』这块做预先计算,

例如:若滑超过 1/2 Y,使 某Decoration失效。

override func invalidationContext(forBoundsChange newBounds: CGRect) -> UICollectionViewLayoutInvalidationContext {
	let invalidationContext = super.invalidationContext(forBoundsChange: newBounds)
	if newBounds.maxY > contentSize.height {
		invalidationContext.invalidateDecorationElements(
                ofKind: "Footer",
                at: [IndexPath(item: 1, section: 0)]
            )
    }
    return invalidationContext
}

复制代码

并在 invalidateLayout(with:) 从context里查询是否对应的Decoration包含在失效名单内,并提前计算好心的Attribute存在持有变量

override func invalidateLayout(with context: UICollectionViewLayoutInvalidationContext) {
	let invalidationContext = context
	if let dic = context.invalidatedDecorationIndexPaths, let idx = dic["Footer"] {
        prepareFooterViewAttributes()
    }
}
复制代码

在询问的时候,将预先计算好的Attribute 回传


override public func layoutAttributesForDecorationView(
        ofKind elementKind: String,
        at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
    return self.footerLayoutAttributes
}

复制代码

特殊标记 (get-only)

这两个特殊标记是会在触发 collectionView.reloadData()时会被系统自动启用,不能自己设置,并且仍会重新进入『配置流程』。

基于系统『自定义』的 InvalidationContext 失效layout

第一步当然是写一个继承UICollectionViewLayoutInvalidationContext的类,并且在UICollectionViewLayout类里覆写以下

override class var invalidationContextClass: AnyClass {
    return InvalidationContext子类名.self
}
复制代码

自定义 InvalidationContext 的好处不外乎就是能自己增加字段,能更清晰也更有逻辑的衔接前后两个函数

例如:沿用前面的例子,但InvalidationContext为自定义,增加一个Bool,判断哪部分需要失效或是需要被标记

class LayoutInvalidationContext: UICollectionViewLayoutInvalidationContext {
    var invalidateFooter = false
}

override func invalidationContext(forBoundsChange newBounds: CGRect) -> UICollectionViewLayoutInvalidationContext {
    let invalidationContext = super.invalidationContext(forBoundsChange: newBounds) as! LayoutInvalidationContext
    guard let collectionView = collectionView else { return invalidationContext }
    let originChanged = !collectionView.bounds.origin.equalTo(newBounds.origin)
    if originChanged && newBounds.maxY > contentSize.height {
        invalidationContext.invalidateFooter = true
    }
    return invalidationContext
}

override func invalidateLayout(with context: UICollectionViewLayoutInvalidationContext) {
    let invalidationContext = context as! LayoutInvalidationContext
    if invalidationContext.invalidateFooter {
        prepareFooterViewAttributes()
        invalidationContext.invalidateDecorationElements(
            ofKind: "Footer",
            at: [IndexPath(item: 1, section: 0)]
        )
    }
    super.invalidateLayout(with: invalidationContext)
}
复制代码

转载于:https://juejin.im/post/5b398af36fb9a00e9a4209b8

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值