setNeedsDisplayInRect:导致整个视图被更新

Posted

技术标签:

【中文标题】setNeedsDisplayInRect:导致整个视图被更新【英文标题】:setNeedsDisplayInRect: causes the whole view to be updated 【发布时间】:2012-05-07 14:56:41 【问题描述】:

我正在开发一个使用 CoreGraphics 进行渲染的绘画应用程序。我的问题是,为了性能,我试图将更新限制在视图中已更改的某些部分。为此,我使用 setNeedsDisplayInRect:,但有时视图会更新其全部内容,从而导致卡顿。这在第 3 代 iPad 上尤为明显,但在模拟器和 iPad2 中也发生的程度较小。我正在尝试消除这种行为。

为了展示这个问题,我使用 Xcode 中的模板创建了一个简单的“单一视图”项目。创建了一个自定义 UIView 子类,我在 xib 文件中将其设置为视图控制器的视图。

将此添加到 UIViewController:

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event

    // Asks that the view refreshes a smal rectangle
    [self.view setNeedsDisplayInRect:CGRectMake(10, 10, 20, 20)];

将此添加到 MyView(我的自定义视图):

- (void)drawRect:(CGRect)rect

    // Just log what is being updated
    NSLog(@"%@", NSStringFromCGRect(rect));

就是这样。如果我在第三十台 iPad(实际设备)上运行该应用程序,日志会显示视图不时在其整体中重新绘制自身(例如非常频繁)。这太令人沮丧了,我没有想法

更新:这里有一些日志。它肯定会显示有时会更新的完整视图。我试图让它完全停止,但不知道如何......

2012-05-04 08:34:01.851 TestUpdateArea[45745:707] 0, 0, 320, 460
2012-05-04 08:34:30.184 TestUpdateArea[45745:707] 0, 0, 320, 460
2012-05-04 08:34:30.197 TestUpdateArea[45745:707] 0, 0, 320, 460
2012-05-04 08:34:30.215 TestUpdateArea[45745:707] 10, 10, 20, 20
2012-05-04 08:34:30.226 TestUpdateArea[45745:707] 10, 10, 20, 20
2012-05-04 08:34:30.242 TestUpdateArea[45745:707] 10, 10, 20, 20
2012-05-04 08:34:30.258 TestUpdateArea[45745:707] 10, 10, 20, 20
2012-05-04 08:34:30.274 TestUpdateArea[45745:707] 10, 10, 20, 20
2012-05-04 08:34:30.290 TestUpdateArea[45745:707] 10, 10, 20, 20
2012-05-04 08:34:30.306 TestUpdateArea[45745:707] 10, 10, 20, 20
2012-05-04 08:34:30.322 TestUpdateArea[45745:707] 10, 10, 20, 20
2012-05-04 08:34:30.338 TestUpdateArea[45745:707] 10, 10, 20, 20
2012-05-04 08:34:30.354 TestUpdateArea[45745:707] 10, 10, 20, 20
2012-05-04 08:34:30.371 TestUpdateArea[45745:707] 10, 10, 20, 20
2012-05-04 08:34:30.387 TestUpdateArea[45745:707] 10, 10, 20, 20
2012-05-04 08:34:30.403 TestUpdateArea[45745:707] 10, 10, 20, 20
2012-05-04 08:34:30.419 TestUpdateArea[45745:707] 10, 10, 20, 20
2012-05-04 08:34:30.439 TestUpdateArea[45745:707] 0, 0, 320, 460
2012-05-04 08:34:30.457 TestUpdateArea[45745:707] 10, 10, 20, 20

此外,如果我停止移动超过 10 秒左右并恢复,它肯定会非常一致地这样做:

2012-05-04 08:34:33.305 TestUpdateArea[45745:707] 10, 10, 20, 20
2012-05-04 08:34:33.321 TestUpdateArea[45745:707] 10, 10, 20, 20
2012-05-04 08:35:00.202 TestUpdateArea[45745:707] 0, 0, 320, 460
2012-05-04 08:35:00.221 TestUpdateArea[45745:707] 0, 0, 320, 460
2012-05-04 08:35:00.234 TestUpdateArea[45745:707] 0, 0, 320, 460
2012-05-04 08:35:00.251 TestUpdateArea[45745:707] 10, 10, 20, 20

【问题讨论】:

在我的例子中,当在 setNeedsDisplayInRect 第一次之后调用 drawRect 时,矩形是完整的边界(不要问我为什么),任何下一次调用它正是我的矩形在 setNeedsDisplayInRect 中指定为参数。您是否多次测试过触摸动作? 它主要发生在设备上。但是,如果您四处移动手指,然后等待大约 10 或 15 秒,然后再次触摸并移动,您可以在模拟器上实现它。在 iPad3 设备上,即使手指移动也会发生这种情况。太烦人了:) 我不久前发布了@这个(***.com/questions/9299018/…)。这似乎只是第一次通话。我从来没有找到原因/修复,只是继续前进,因为我不需要那种级别的性能优化。您可以测试 rect == 视图大小而不是绘制吗?也许不是一个选择。 我尝试测试 rect,但不幸的是,如果我退出 drawRect 方法而不绘制 rect 覆盖的内容,则视图最终为空,因为调用 drawRect 会自动清除视图。这太令人沮丧了,听起来很简单。 奇怪的问题。您是否考虑过通过将每个绘图动作放到它自己的视图或图层中来解决它,然后只是将它们堆叠在一起?这样,您只会“弄脏”并且每次都必须重新绘制一个新图层,并且您将利用合成引擎来处理需要更新哪些其他图层。如果您尝试这样做,请告诉我们它是否能提供更好的性能。 【参考方案1】:

接受的答案是错误的(或至少具有误导性)。

这是一个简单的 setNeedsDisplayInRect 代码示例:按广告宣传:

http://charcoaldesign.co.uk/resources/chalkboard.zip

我怀疑 Apple 文档试图解释这样一个事实,即在 ios 上,每个视图支持图像都转换为 OpenGL 纹理,并且整个屏幕每帧都重新合成。这与您的代码是否必须清除和重绘视图支持纹理的全部内容的问题不同。

通过使用 setNeedsDisplayInRect: 您可以避免昂贵的重绘内容,这些内容不会在帧之间发生变化(它使用核心图形并且没有硬件加速)。无论如何,整个屏幕仍然会被重绘,但这并不重要,因为它是由 GPU 硬件加速的,这与 drawRect 中的代码不同:

【讨论】:

【参考方案2】:

检查这个苹果document

他们说:

由于 iPhone/iPod touch/iPad 更新屏幕的方式, 如果您调用-setNeedsDisplayInRect:-setNeedsDisplay:.

还有

每个 UIView 都被视为一个元素。当您通过调用-setNeedsDisplayInRect:-setNeedsDisplay: 请求部分或全部重绘时,整个视图将被标记为更新。

所以我认为您需要使用子视图来更新整个视图的独立矩形。

【讨论】:

这没有意义。那么有这两种方法有什么意义呢。为什么它有时会起作用? 我读过一些 iOS 5 中的错误 .. 你在 iOS 4 上试过了吗? 它在 iOS4 中的作用要小得多。 这个答案具有误导性。下面@Nick Lockwood 的回答更准确。【参考方案3】:

我有一个类似的问题:即只想通过调用 setNeedsDisplayInRect 来更新 UIView 的一部分:我传递了正确大小的有效 CGRect(通过断点和日志证明)。在 iOS 4.2.1 下(在我的 iPhone 3G 上),UIView 保留其原始图像,只更新由 CGRect 指定的部分,但在 iOS 5.1 下(在我的 iPhone 4S 上),整个 UIView 被重绘为透明黑色(如何黑色可以透明超出了我的范围),之后正确绘制了 CGRect 指定的区域。似乎是:Apple 的一个错误是他们没有正确实现 setNeedsDisplayInRect: 并且只是将它传递给 setNeedsDisplay:;或者这是一个基于新 iOS 如何处理与实际屏幕架构交互的故意决定。无论哪种方式,目前看来,开发人员要么不得不在每次发生图形更改时都承担更新整个 UIView 的成本,要么使用一些 iOS 检测并根据所处理的 iOS 实现不同的屏幕绘制代码。

我已经对 UIView 属性 clearsContextBeforeDrawing 进行了一些测试(根据文档,它应该允许有针对性的绘图操作),但是 YES 和 NO 的结果是相同的,所以它似乎是绘图代码的实现。

至于更新期间 CGRect 的大小不正确:它是在绘图调用之前还是之后设置的其他地方。我在调用 setNeedsDisplayInRect: 之前设置了我的 CGRect,然后在 drawRect 方法的最后几行将其设置为 CGRectNull(如果系统调用 drawRect 方法,我的代码将不会触发)。回到你的代码 sn-p 后,我可以看到它被设置为内联,这意味着它是一个系统调用,它正在重绘整个 UIView,因此 CGRect 是整个视图。

我会继续深入研究这一点,但我担心在 Apple 披露他们对 setNeedsDisplayInRect: 进行更改的原因或修复明显的错误之前,我们可能会考虑为每次图形更新更新 UIView 的全部内容(这可能非常“昂贵”)。

【讨论】:

这就是我所担心的。我向 Apple 开了一张技术支持票,他们默默地关闭了它,然后一言不发地把票的费用记回了我的账户……【参考方案4】:

我没有很多可用的设备,但无论如何我有好消息!作为多年来一直希望在 iOS 上实现此功能的人,我可以断言 iOS8.0+ 带来了此功能。我已经在模拟器和设备上尝试过 8.0、8.1、8.2

自 iOS6 以来我还没有尝试过这个。所以我不确定它是否在 iOS7 中可用。也许其他人可以确认。

请与我一起为 setNeedsDisplayInRect: 按记录运行而无休止地欢呼!

【讨论】:

以上是关于setNeedsDisplayInRect:导致整个视图被更新的主要内容,如果未能解决你的问题,请参考以下文章

iOS:当视图中有两个小的不相邻区域需要重绘时,调用两次 setNeedsDisplayInRect 是不是更快?

在 iOS 上,如果 drawRect 不使用传入的矩形,那么 [self setNeedsDisplayInRect:rect] 传入的矩形是不是重要?

为啥过渡框阴影会导致整页重绘?

可调整大小导致其下方的其他元素移动

Asp.Net jQuery $.getJSON 有时会导致整页回发?

单击面板会导致异步回发,但单击子项会导致整页回发