如何在 UICollectionView 中显示 UIView 的一部分?

Posted

技术标签:

【中文标题】如何在 UICollectionView 中显示 UIView 的一部分?【英文标题】:How to draw parts of a UIView as it becomes visible inside UICollectionView? 【发布时间】:2018-07-20 08:11:04 【问题描述】:

android 上,我在水平滚动列表上有可能具有较大宽度的项目视图。当视图的一部分在列表中可见时,视图会加载并绘制块“图像”。这是避免一次绘制所有图像的优化,因为它会浪费且缓慢。绘制的内容本质上是一个音频波形。 按需要工作的方式做事 我不能将块拆分为列表中的单个视图项。 由于 android 绘图架构的工作原理,此策略非常有效。

现在我正在尝试在 ios 中使用类似的模式,但遇到了一些麻烦,我不太确定如何提出解决方案。

在 iOS 中,我使用 UICollectionView 绘制大宽度单元格,我们需要同样优化加载和仅绘制可见块。


解决方案 1: 检查UIView 的哪些部分是可见的并只绘制那些可见的块。此解决方案的问题在于,当UICollectionView 滚动时,UIView 不会绘制下一个可见的卡盘。以下是我所说的示例。

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

滚动一下 显示黑色并且没有加载任何内容,因为没有提示需要再次绘制视图,因此我们无法加载下一个可见块。


解决方案 2: 使用由CATiledLayer 支持的自定义UIView。这很完美,因为它会在滚动UICollectionView 时绘制瓷砖,因为它们变得可见。

问题是如果定义了shouldDrawOnMainThread,则绘制发生在后台线程或下一个绘制周期。当UIView 调整大小或我的内部缩放逻辑启动时,这会带来问题。事情发生了变化,因为绘图周期与调整视图大小不同步。


那么我怎么能像CATiledLayer 这样得到通知,告诉我某个部分变得可见并且我可以像CALayer 支持UIView 一样正常绘制呢?


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

- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes

更新 2 经过多次测试和玩耍。使用解决方案 1 不是一种选择。当UIView 达到一个巨大的宽度时,内存使用量会飙升,而使用CATiledLayer 的内存使用量是最小的,正如我猜想的那样。 :/

【问题讨论】:

你真的试过func draw(_ rect: CGRect)吗? @MichaelVorontsov 好吧,这并不是真正的问题。 :P 绘图没问题。问题是仅触发可见区域的绘图。在我的更新 #2 中,我遇到了一个限制,使 CATiledLayer 或多或少地成为了答案。 :// func setNeedsDisplay(CGRect) ?仅当您要求绘制特定矩形时,您可以提供额外的标志来进行绘制,并且在标志下降时不绘制任何内容。 【参考方案1】:

不知道能不能完全关闭效果。您可以通过将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;
    

如果您想实现自己的平铺 层,您应该放弃平铺并尝试计算视图的可见部分(截面和缩放级别)。比您只能将这部分绘制为图层的全部内容。

显然,当滚动视图滚动时,没有简单的方法可以重新绘制视图。但是你可以实现scrollViewDidScroll:,并手动触发重绘。 visibleRect 方法总是返回图层的完整区域,但是你可以使用

CGRect theVisibleRect = CGRectIntersection(self.frame, self.superlayer.bounds);

或在 Swift 中

let theVisibleRect = frame.intersection(superlayer.bounds)

确定图层的可见区域。

【讨论】:

感谢您的回答。所以我提到的奇怪问题不是动画的淡入淡出。问题是我需要在 UIView 调整大小后立即绘制我们的缓存图像。由于 CATiledLayer 发送在 bg 线程上绘制的请求,因此 UIView 的大小和显示的图层内容之间存在同步问题。当视图从左侧调整大小时,这会导致抖动效果。 @Jona:是的,我知道这种影响,我认为你无法完全摆脱它。但是减少淡入淡出持续时间可以减轻它。您也可以尝试实现 shouldDrawOnMainThread 类方法,但这是一个私有方法,您有被 App Store 拒绝的风险。我已经为此更新了我的帖子。 啊,太酷了!谢谢你分享这个。我刚试过那个方法。它确实会导致在主线程上进行绘图。然而,不幸的是,它会将绘图更新发布到下一个主线程周期。所以同步问题仍然存在。我希望我可以实现我自己的自定义 CATiledLayer。我只是不知道如何启用它仅绘制可见图块的行为。 @Jona:我认为你无法在下一个运行循环周期中摆脱绘图,即使你正在实现自己的平铺图层类。你不能这样做,因为绘图方法的调用总是来自运行循环。您不能直接在屏幕上绘图。 因此,使用标准 CALayer 支持的 UIView 在调整大小时一切正常。更新 UI 的调用发生在更改视图大小的同一周期。所以一切都正确同步。我想要 CATiledLayer 的行为,它告诉我绘制视图的某个部分,但全部来自同一个主线程周期。它必须以某种方式成为可能! :P 希望,我没有让你感到困惑......

以上是关于如何在 UICollectionView 中显示 UIView 的一部分?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Alamofire 在 UICollectionView 单元格中使用标签显示图像?

如何在 UICollectionView 中每行显示 3 个单元格?

如何在Objective c中显示UICollectionView的水平滚动?

如何在 UICollectionView 的部分标题中显示图像和标签?

如何在不同的 UICollectionView 单元格中显示不同的视频?

如何在 UICollectionView 中显示 UIView 的一部分?