I'm working on a painting app that uses CoreGraphics for rendering. My problem is, for performance, I am trying to limit the updates to only certain portions of the view that have changed. For that, I use setNeedsDisplayInRect:, but sometimes the view updates its entire content, causing stuttering. This is particularly evident on the 3rd generation iPads, but it also happens in the simulator and the iPad2 to a lesser degree. I am trying to remove this behavior.
To showcase the problem, I created a simple "single view" project using the template in Xcode. Created a custom UIView subclass which I set as the view controller's view in the xib file.
Added this to the UIViewController:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
// Asks that the view refreshes a smal rectangle
[self.view setNeedsDisplayInRect:CGRectMake(10, 10, 20, 20)];
}
Added this to the MyView (my custom view):
- (void)drawRect:(CGRect)rect
{
// Just log what is being updated
NSLog(@"%@", NSStringFromCGRect(rect));
}
And that's it. If I run the app on the 3rd ten iPad (the actual device), the logs show the view repainting itself in its entierty from time to time (like very frequently). This is so frustrating and I am out of ideas
UPDATE: Here are some logs. It definitely shows the full view being updated sometimes. I am trying to make it stop completely but can't figure how to...
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}}
Also if I stop moving for over 10s or so and resume, it definitely does it very consistently:
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}}
Check this apple document
they said :
because of the way that iPhone/iPod touch/iPad updates its screen, the
entire view will be redrawn if you call -setNeedsDisplayInRect:
or
-setNeedsDisplay:
.
Also
Each UIView is treated as a single element. When you request a redraw, in part or whole, by calling -setNeedsDisplayInRect:
or -setNeedsDisplay:
, the entire view will be marked for updates.
so I think you need to use subviews to update independent rect of the whole View.
The accepted answer is wrong (or at least misleading).
Here's a simple code example of setNeedsDisplayInRect: working as advertised:
http://charcoaldesign.co.uk/resources/chalkboard.zip
I suspect that the Apple documentation is trying to explain the fact that on iOS every view backing image is converted to an OpenGL texture and the entire screen is re-composited every frame. That distinct from the question of whether your code has to clear and redraw the entire contents of the view's backing texture or not.
By using setNeedsDisplayInRect: you can avoid expensive redrawing of content that doesn't change between frames (which uses core graphics and is not hardware accelerated). The whole screen will still be redrawn regardless, but that doesn't matter because it's hardware accelerated by the GPU, unlike the code in drawRect:
I have a similar issue: namely wanting to update only a portion of a UIView with a call to setNeedsDisplayInRect: which I pass a valid CGRect of the correct size (proved with Break Points, and Logs). Under iOS 4.2.1 (on my iPhone 3G) the UIView keeps its original image and only updates the portion specified by the CGRect, but under iOS 5.1 (on my iPhone 4S) the entire UIView is redrawn in transparent black (how black can be transparent is beyond me), with the area specified by the CGRect being drawn in correctly after. It seems to be either: an error on Apple's part that they are not implementing setNeedsDisplayInRect: correctly and just passing it to setNeedsDisplay:; or it was a wilful decision based on how the new iOS handles interaction with the actual screen architecture. Either way, it would seem that for the time being developers will either have to wear the cost of updating the entire UIView every time a graphical change occurs, or use some iOS detection and implement different screen drawing code depending on the iOS being dealt with.
I have done some testing with the UIView property clearsContextBeforeDrawing (which according to the documentation should allow targeted drawing operations), but the results are identical for both YES and NO, so it would appear to be the implementation of the drawing code.
As for the CGRect being of the incorrect size during updates: is it being set somewhere else prior to, or after, the drawing call. I set my CGRect just before the call to setNeedsDisplayInRect:, and then set it to CGRectNull on the last few lines of the drawRect method (this is so if the drawRect method is called by the system my code will not fire). After going back to your code snippet I can see it gets set in-line, which means it's a System Call that's redrawing the entire UIView, and hence why the CGRect is the entire view.
I'll keep digging into this, but I fear that until Apple either disclose their reasoning for the change to setNeedsDisplayInRect:, or fix the apparent bug, we may be looking at updating the entire contents of a UIView for every graphical update (which could be potentially very 'expensive').
I don't have a lot of devices available to me, but I have great news anyway! As someone who has been wishing for this functionality on iOS for years, I can assert that iOS8.0+ brings this functionality. I've tried on both simulator and device for 8.0, 8.1, 8.2
I haven't tried this since iOS6. So I'm not sure if its available in iOS7 or not. Perhaps someone else can confirm.
Please join with me in cheering endlessly for setNeedsDisplayInRect:
functioning as documented!