有没有办法使drawRect工作正常? [英] Is there a way to make drawRect work right NOW?

查看:122
本文介绍了有没有办法使drawRect工作正常?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

原始问题...................................... 如果你是drawRect的高级用户,你会知道当然drawRect不会实际运行直到所有处理完成。



setNeedsDisplay将视图标记为无效和操作系统,基本上等待所有处理完成。在您想要拥有的常见情况下,这可能是令人不安的:




  • 视图控制器1

  • 启动一些功能2

  • 增加3

    • 创建一个越来越复杂的图稿和4

    • 在每个步骤,你所有的工作完成之前,你setNeedsDisplay(错误!)5





当然,当你做上面的1-6,所有这一切发生的是drawRect只运行 一次 / strong>之后。



您的目标是在第5点刷新视图。 b




解决原始问题........................ ......................



总之,可以 大绘画,并调用前台的UI更新或 (B)有争议的 有四个立即方法建议不使用后台进程。对于什么工作的结果,运行演示程序。






汤姆·斯威夫特推出的令人惊讶的替代解决方案。 .................



汤姆·斯威夫特解释了操纵运行循环。以下是触发运行循环的方法:



[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate date]];



这是一个真正令人惊叹的工程。当然,在操作运行循环时应​​该非常小心,并且很多人指出,这种方法是严格的专家。






出现的奇怪问题....................................... .......



即使有很多方法可以工作,一个奇怪的进步 - 慢下来的神器,你会在演示中清楚地看到。



滚动到我在下面粘贴的答案,显示控制台输出 - 你可以看到



这是新的SO问题:

神秘的渐进减速运行循环中的问题/ drawRect



这里是演示应用程序的V2 ...

http://www.fileswap.com/dl/p8lU3gAi/stepwiseDrawingV2.zip.html



您将看到它测试所有五种方法,

  #ifdef TOMSWIFTMETHOD 
[self setNeedsDisplay] ;
[[NSRunLoop currentRunLoop]
runMode:NSDefaultRunLoopMode beforeDate:[NSDate date]];
#endif
#ifdef HOTPAW
[self setNeedsDisplay];
[CATransaction flush];
#endif
#ifdef LLOYDMETHOD
[CATransaction begin];
[self setNeedsDisplay];
[CATransaction commit];
#endif
#ifdef DDLONG
[self setNeedsDisplay];
[[self layer] displayIfNeeded];
#enddef
#ifdef BACKGROUNDMETHOD
//这里,绘画正在bg中完成,我们一直是
//在前台调用inval
[self setNeedsDisplay];
#endif




  • 哪些方法工作,哪些不工作。


  • 您可以看到奇怪的渐进减速。为什么会发生?


  • 你可以看到有争议的TOMSWIFT方法,实际上没有任何问题响应。点击以随时响应。 (但仍然是奇怪的进步 - 慢下来问题)




所以令人难以置信的事情是这奇怪的 -slow-down:在每次迭代时,由于未知的原因,循环所需的时间减少。注意,这适用于正确地(背景外观)或使用立即方法之一。






实用解决方案............... ........



对于将来阅读的人,如果你实际上无法使用因为神秘进步的减速... Felz和Void在其他具体问题中都提出了惊人的解决方案,希望它有帮助。

解决方案

对用户界面的更新发生在当前通过运行循环的结尾。这些更新在主线程上执行,所以在主线程中长时间运行的任何东西(冗长的计算等)将阻止接口更新启动。此外,在主线程上运行一段时间的任何东西也会导致您的触摸处理无响应。



这意味着没有办法强制从主线程上运行的进程中的某个其他点发生UI刷新。前面的语句不完全正确,正如汤姆的回答所示。您可以允许运行循环在主线程执行的操作中完成。但是,这仍然可能会降低应用程序的响应速度。



一般来说,建议您将需要一段时间执行的任何内容移动到后台线程,以便用户界面可以保持响应。但是,您希望对UI执行的任何更新都需要在主线程上完成。



也许最简单的方法是在Snow Leopard和iOS 4.0+是使用块,如下面的基本示例:

  dispatch_queue_t main_queue = dispatch_get_main_queue 
dispatch_async(queue,^ {
//做一些工作
dispatch_async(main_queue,^ {
//更新UI
});
} );

做一些工作可能是一个冗长的计算,或循环多个值的操作。在此示例中,仅在操作结束时更新UI,但是如果您希望在UI中进行连续进度跟踪,则可以将调度放置到需要执行UI更新的主队列。



对于较旧的操作系统版本,您可以手动或通过NSOperation中断后台线程。对于手动背景线程,您可以使用

  [NSThread detachNewThreadSelector:@selector(doWork)toTarget:self withObject:nil]; 

  [self performSelectorInBackground:@selector(doWork)withObject:nil]; 

,然后更新UI,您可以使用

  [self performSelectorOnMainThread:@selector(updateProgress)withObject:nil waitUntilDone:NO]; 

请注意,我发现上一个方法中的NO参数需要获得不断的UI更新同时处理持续进度条。



此示例应用程序我为我的类创建说明如何使用NSOperations和队列执行后台工作,然后更新UI完成时。此外,我的 Molecules 应用程序使用后台线程来处理新结构,状态栏随着此进度而更新。您可以下载源代码来查看我是如何实现的。


The original question...............................................

If you are an advanced user of drawRect, you will know that of course drawRect will not actually run until "all processing is finished."

setNeedsDisplay flags a view as invalidated and the OS, and basically waits until all processing is done. This can be infuriating in the common situation where you want to have:

  • a view controller 1
  • starts some function 2
  • which incrementally 3
    • creates a more and more complicated artwork and 4
    • at each step, you setNeedsDisplay (wrong!) 5
  • until all the work is done 6

Of course, when you do the above 1-6, all that happens is that drawRect is run once only after step 6.

Your goal is for the view to be refreshed at point 5. What to do?


Solution to the original question..............................................

In a word, you can (A) background the large painting, and call to the foreground for UI updates or (B) arguably controversially there are four 'immediate' methods suggested that do not use a background process. For the result of what works, run the demo program. It has #defines for all five methods.


Truly astounding alternate solution introduced by Tom Swift..................

Tom Swift has explained the amazing idea of quite simply manipulating the run loop. Here's how you trigger the run loop:

[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [NSDate date]];

This is a truly amazing piece of engineering. Of course one should be extremely careful when manipulating the run loop and as many pointed out this approach is strictly for experts.


The Bizarre Problem That Arises..............................................

Even though a number of the methods work, they don't actually "work" because there is a bizarre progressive-slow-down artifact you will see clearly in the demo.

Scroll to the 'answer' I pasted in below, showing the console output - you can see how it progressively slows.

Here's the new SO question:
Mysterious "progressive slowing" problem in run loop / drawRect

Here is V2 of the demo app...
http://www.fileswap.com/dl/p8lU3gAi/stepwiseDrawingV2.zip.html

You will see it tests all five methods,

#ifdef TOMSWIFTMETHOD
 [self setNeedsDisplay];
 [[NSRunLoop currentRunLoop]
      runMode:NSDefaultRunLoopMode beforeDate:[NSDate date]];
#endif
#ifdef HOTPAW
 [self setNeedsDisplay];
 [CATransaction flush];
#endif
#ifdef LLOYDMETHOD
 [CATransaction begin];
 [self setNeedsDisplay];
 [CATransaction commit];
#endif
#ifdef DDLONG
 [self setNeedsDisplay];
 [[self layer] displayIfNeeded];
#endif
#ifdef BACKGROUNDMETHOD
 // here, the painting is being done in the bg, we have been
 // called here in the foreground to inval
 [self setNeedsDisplay];
#endif

  • You can see for yourself which methods work and which do not.

  • you can see the bizarre "progressive-slow-down". why does it happen?

  • you can see with the controversial TOMSWIFT method, there is actually no problem at all with responsiveness. tap for response at any time. (but still the bizarre "progressive-slow-down" problem)

So the overwhelming thing is this weird "progressive-slow-down": on each iteration, for unknown reasons, the time taken for a loop deceases. Note that this applies to both doing it "properly" (background look) or using one of the 'immediate' methods.


Practical solutions ........................

For anyone reading in the future, if you are actually unable to get this to work in production code because of the "mystery progressive slowdown" ... Felz and Void have each presented astounding solutions in the other specific question, hope it helps.

解决方案

Updates to the user interface happen at the end of the current pass through the run loop. These updates are performed on the main thread, so anything that runs for a long time in the main thread (lengthy calculations, etc.) will prevent the interface updates from being started. Additionally, anything that runs for a while on the main thread will also cause your touch handling to be unresponsive.

This means that there is no way to "force" a UI refresh to occur from some other point in a process running on the main thread. The previous statement is not entirely correct, as Tom's answer shows. You can allow the run loop to come to completion in the middle of operations performed on the main thread. However, this still may reduce the responsiveness of your application.

In general, it is recommended that you move anything that takes a while to perform to a background thread so that the user interface can remain responsive. However, any updates you wish to perform to the UI need to be done back on the main thread.

Perhaps the easiest way to do this under Snow Leopard and iOS 4.0+ is to use blocks, like in the following rudimentary sample:

dispatch_queue_t main_queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
    // Do some work
    dispatch_async(main_queue, ^{
        // Update the UI
    });
});

The Do some work part of the above could be a lengthy calculation, or an operation that loops over multiple values. In this example, the UI is only updated at the end of the operation, but if you wanted continuous progress tracking in your UI, you could place the dispatch to the main queue where ever you needed a UI update to be performed.

For older OS versions, you can break off a background thread manually or through an NSOperation. For manual background threading, you can use

[NSThread detachNewThreadSelector:@selector(doWork) toTarget:self withObject:nil];

or

[self performSelectorInBackground:@selector(doWork) withObject:nil];

and then to update the UI you can use

[self performSelectorOnMainThread:@selector(updateProgress) withObject:nil waitUntilDone:NO];

Note that I've found the NO argument in the previous method to be needed to get constant UI updates while dealing with a continuous progress bar.

This sample application I created for my class illustrates how to use both NSOperations and queues for performing background work and then updating the UI when done. Also, my Molecules application uses background threads for processing new structures, with a status bar that is updated as this progresses. You can download the source code to see how I achieved this.

这篇关于有没有办法使drawRect工作正常?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆