如何在 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 的部分标题中显示图像和标签?