如何在UICollectionView中可见时绘制UIView的部分内容?

6
在Android上,我有一个水平滚动列表中可能具有大宽度的项目视图。随着视图的部分变得可见于列表,该视图会加载并绘制"图像"块。这是一种优化方式,避免了一次性绘制所有图像,因为这样做既浪费又缓慢。绘制的内容本质上是音频波形。由于需要工作的方式,我无法将块拆分为列表上的单个视图项。这种策略非常适用于Android绘图架构的工作方式。
现在我正在尝试在iOS中使用类似的模式,但我遇到了一些问题,而且我不太确定如何找出解决方案。
在iOS中,我们使用UICollectionView绘制大宽度的单元格,并且我们需要相同的优化来仅加载和绘制可见的块。
解决方案1:
检查UIView的哪些部分是可见的,仅绘制那些可见的块。然而,这种解决方案的问题在于,随着UICollectionView的滚动,UIView不会绘制正在变得可见的下一块。以下是相关示例。

UIView加载初始块
这些可以通过它们不同的块颜色来看到: enter image description here

向下滚动一点
黑色被显示出来,因为没有提示需要重新绘制视图,所以我们无法加载下一个可见块。 enter image description here


解决方案2:
使用自定义的UIView,由CATiledLayer支持。这个方法非常完美,因为它会在滚动UICollectionView时绘制可见的瓦片。

问题是绘制发生在后台线程或下一个绘图周期(如果定义了shouldDrawOnMainThread)。当UIView被调整大小或我的内部缩放逻辑启动时,会出现问题。因为绘图周期未与视图调整大小同步,所以事物会发生变化。


那么我该如何像 CATiledLayer 一样被通知某个部分正在变得可见,然后可以像支持 UIViewCALayer 一样正常绘制呢?


更新 1
我正在研究使用preferredLayoutAttributesFittingAttributes作为一种检查是否需要绘制新块的方法。这会在每次由于滚动而将单元格移动到新位置时调用。希望这不是个坏主意。 :)
- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes

更新2
经过大量测试和试验,使用解决方案1并不可行。当UIView达到巨大的宽度时,内存使用量会飙升,而使用CATiledLayer时内存使用量最小,正如我所料。 :/

你试过实际使用 func draw(_ rect: CGRect) 吗? - MichaelV
@MichaelVorontsov 嗯,那不是真正的问题。:P 绘图没有问题。问题在于仅触发可见区域的绘制。在我的更新#2中,我遇到了一个限制,使CATiledLayer成为更或多或少的答案。 :/ - Jona
func setNeedsDisplay(CGRect)?您可以提供额外的标志,仅在要求绘制特定矩形时才进行绘制,并在标志关闭时不进行绘制。 - MichaelV
1个回答

2
我不确定你是否可以完全关闭这个效果。你可以通过将fadeDuration设置为0来使它变得更加柔和。要做到这一点,你必须创建一个CATiledLayer的子类并重写类方法fadeDuration
你也可以尝试实现shouldDrawOnMainThread类方法,但这是一个私有方法,你可能会面临App Store拒绝的风险:
@implementation MyTiledLayer

+(CFTimeInterval)fadeDuration {
    return 0.0;
}

+ (BOOL)shouldDrawOnMainThread {
    return YES;
}

@end

或者使用Swift:

class MyTiledLayer: CATiledLayer {
    override class func fadeDuration() -> CFTimeInterval {
        return 0.0
    }

    class func shouldDrawOnMainThread() -> Bool {
        return true;
    }
}

如果您想实现自己的“平铺”图层,您应该放弃使用瓷砖,并尝试计算视图的可见部分(部分和缩放级别)。然后,您可以仅绘制该部分作为整个图层内容。
显然,在滚动ScrollView时重新绘制视图没有简单的方法。但是,您可以实现“scrollViewDidScroll:”,并手动触发重绘。 “visibleRect”方法始终返回图层的完整区域,但您可以使用
CGRect theVisibleRect = CGRectIntersection(self.frame, self.superlayer.bounds);

或者使用Swift。
let theVisibleRect = frame.intersection(superlayer.bounds)

确定图层的可见区域。


@Jona:我认为即使你实现了自己的瓦片层类,也无法在下一个运行循环周期中摆脱绘图。这是因为绘图方法的调用始终来自运行循环。你不能直接向屏幕绘制。 - clemens
使用标准的CALayer支持的UIView进行调整大小时,一切都可以完美工作。对UI的更新调用发生在视图大小更改的同一周期内。所以一切都能正确同步。我想要的是CATiledLayer的行为,它告诉我绘制视图的某个部分,但所有操作都要在同一个主线程周期中完成。这肯定有办法实现!希望我没有让你困惑... - Jona
谢谢您的建议。那是我开始的方式。它运行得很完美,但当您开始滚动大的UIView时,它不会给我任何提示,表明现在可见的是特定的部分。我尝试重写各种方法,以查看集合视图滚动时调用的方法,但我找不到任何东西。因此,唯一的方法就是在滚动时使collectionview失效,但这似乎有些过度了。 - Jona
是的,我确实使用带有重新绘制的contentMode。顺便说一下,在我们之前的讨论后,我对我的问题进行了进一步改进。 - Jona
@Jona:我已经扩展了我的帖子。 - clemens
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接