Say, for an iOS app, if a user slides his finger on the screen, and then 50,000 dots are recorded. If the drawing is done for all these dots in drawRect, then next time the user touched the 50,001st dot, at the end of touchesMoved the following line
[self.view setNeedsDisplay];
will cause drawRect to run again and have all 50,001 dots drawn again. So for every 1 new dot (for any new movement of finger), all 50,001 dots will need to be redrawn and it is not an efficient method.
I tried just drawing the last dot in drawRect, and it will not "add to" the existing view, but "wipe everything out" and then draw one dot.
Is there a way to
1) draw that 1 extra dot without needing to have drawRect called?
2) or, can drawRect draw one extra dot without first wiping the whole screen out?
One approach for this would be to render into an image when the touches end, and then keep adding to the image as more paths are generated. You can store the paths in an array if you need and undo buffer or otherwise need to regenerate the drawing.
(you will need more code than this, obviously)
UIGraphicsBeginImageContextWithOptions(size, NO, 1.0);
CGContextRef context = UIGraphicsGetCurrentContext();
// do some drawing
UIImage * image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Oh - fwiw, I have found setNeedsDisplayInRect: to be odd/buggy in iOS5. The first call to it is the full view rect, not the rect passed in as a param. At least that is what I found when I tried to use it. Maybe there is some implementation detail I overlooked.
Take a look at the method -(void)setNeedsDisplayInRect:. You can ask your view to redraw a specified rect of itself, but be careful when implementing drawRect: method -where you need to assume that the passed rect argument is just a piece of the whole rect of your view and probably your drawing logic will differ. Also you may consider the clearContextBeforeDrawing property of UIView.
Related
I was overriding the drawRect:(CGRect)rect method while making a simple iOS application. In the book I was reading, the bounds were defined using self.bounds as shown here:
- (void)drawRect:(CGRect)rect
{
CGRect bounds = self.bounds;
//rest of drawing code here
}
I noticed that in the book, the rest of the method did not even use the rect argument and worked fine. I assumed that rect would set the bounds in the view, so I tried the following:
- (void)drawRect:(CGRect)rect
{
CGRect bounds = rect;
//rest of drawing code here
}
(Obviously, I would not even need to make bounds equal to rect since I can refer directly to rect within the method.) I tried both ways and they yielded the same result. So are self.bounds and rect equal? If they are, I am assuming rect is used to set the bounds of the current view somewhere behind the scenes. But if they are not, what is the use of having rect as an argument to a method that does not even use it? Am I overlooking something obvious?
rect tells you which area you need to draw. It will always be less than or equal to self.bounds. As per the documentation (emphasis added):
The portion of the view’s bounds that needs to be updated. The first
time your view is drawn, this rectangle is typically the entire
visible bounds of your view. However, during subsequent drawing
operations, the rectangle may specify only part of your view.
If it's less efficient for you to draw subdivisions of your view then you might as well draw the whole thing.
In practice just drawing the whole thing is pretty much never a bottleneck so most people just do that as per the rule that the simplest code is preferable unless or until performance requires a different approach.
drawRect is written to pass in a rectangle that the method is supposed to draw. It's possible that the system may decide that only a portion of the view (perhaps because most of the view is covered by another view.
If only a portion of the view needs to be drawn, it can be faster to only draw that part.
As Tommy said while I was typing my answer, it is sometimes easier to just draw the entire view.
I'm working on a educational app involving complex scripts in which I paint parts of different 'letters' different colours. UILabel is out of the question, so I've drilled down into Core Text and am having a surprisingly successful go of painting glyphs in CALayers.
What I haven't managed to do is animate the size of my custom drawn text. Basically I have text on 'tiles' (CALayers) that move around the screen. The moving around is okay, but now I want to zoom in on the ones that users press.
My idea is to try to cache a 'full resolution' tile and then draw it to scale during an animation of an image bounds. So far I've tried to draw and cache and then redraw such a tile in the following way:
UIGraphicsBeginImageContext(CGSizeMake(50, 50));
CGContextRef context = UIGraphicsGetCurrentContext();
//do some drawing...
myTextImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Then in [CALayer drawInContext:(CGContextRef)context],
I call [myTextImage drawAtPoint:CGPointZero].
When I run the app, the console shows <Error>: CGContextDrawImage: invalid context 0x0. Meanwhile I can perfectly while just continue to draw text in context in the same method even after that error is logged.
So I have two questions: (1) Why isn't this working? Should I be using CGBitmap instead?
And more important: (2) Is there a smarter way of solving the overall problem? Maybe storing my text as paths and then somehow getting CAAnimation to draw it at different scales as the bounds of the enclosing CALayer change?
Okay, this is much easier than I thought. Simply draw the text in the drawInContext: of a CALayer inside of a UIView. Then animate the view using the transform property, and the text will shrink or expand as you like.
Just pay attention to scaling so that the text doesn't get blocky. The easiest way to do that is to make sure the transform scale factors do not go above 1. In other words, make the 'default' 1:1 size of your UIView the largest size you ever want to display it.
It seems that the standard way to draw dots, lines, circles, and Bezier paths is to draw them in inside of drawRect. We don't directly call drawRect, but just let iOS call it and we can use [self setNeedsDisplay] to tell iOS to try to call drawRect when it can...
It also seems that we cannot rely on
[self setClearsContextBeforeDrawing: NO];
to not clear the background of the view before calling drawRect. Some details are in this question: UIView: how to do non-destructive drawing?
How about directly drawing on the screen -- without putting those code in drawRect. For example, in ViewController.m, have some code that directly draw dots, lines, circles on the screen. Is that possible?
Without having to drop into OpenGL, the closest you can do to get around the erasure is to convert the context as an image using something like CGBitmapContextCreateImage. From there, you can retain the image in memory (or write it to disk if necessary), and then when you redraw the view, you first draw this original image into the context and then overlay it with new content.
That is, if
[self setNeedsDisplayInRect:rect];
is called, and rect is very carefully calculated for the region that needs to be redrawn, but if our drawRect code doesn't care about rect and draw everything anyway, can the iOS system still somehow improve the drawing speed? (or possibly improve very little?) This question probably requires somebody who is very familiar with UIKit/CoreGraphics.
There are a few ways the answer could be yes:
You can clip to the rectangle, in which case anything outside it won't be painted, even if you draw in it. Drawing outside the rectangle won't be free, but it will be cheaper. iOS can't do this for you because you might deliberately ignore the rect, or use the rect but also draw something else elsewhere in your bounds unconditionally. (Though that other thing should probably be another view.)
Even if your current drawRect: doesn't use the rectangle, you might go back to that code later to optimize it. As you're probably aware, one very good way to do that—if it's at all possible—is to use the rectangle to decide what you draw. Even if you're not doing that now, you may do it in the future, and specifying changed rects now means that many fewer things to change then.
A corollary to #3 is that even if what you're drawing now can't be so optimized, you may decide in a future major version to completely change what the view draws to something that can. Again, specifying changed rects now means that many fewer things to do in the future.
Subviews. If your view doesn't actually draw some of the things that the user sees in it, but rather delegates (not in the Cocoa/Cocoa Touch sense) those things to subviews, then you might override setNeedsDisplayInRect: to send setNeedsDisplay: messages to subviews—and only the subviews whose frames intersect the rect—before calling super. (And UIView's implementation might already do this. You should test it.)
If your drawRect: implementation ignores the rect passed in and draws the entire view, any optimization of the rect passed to setNeedsDisplayInRect: is for nought.
The "needs display" rect is passed straight through; the only thing it's there for is for the drawRect: implementation to use for ignoring unnecessary drawing. Once your drawRect: implementation is entered, the system can't tell whether your drawing outside the passed rect is intentional, so all the drawing really happens (performance implications included).
Depending on what you're drawing and how, it's not too difficult to restrict your drawRect: implementation to at least make some use of the rect passed in. Everything you want to draw has a bounding rect, whether it's a chunk of text or a bezier path or an image or just a rect you're filling with some color. Surround each bit to drawing with a CGRectIntersectsRect() test -- you won't completely restrict your drawing to the passed in rect that way, but you'll at least eliminate anything that doesn't touch that rect from needing to be drawn.
I'm trying to draw an animated growing line using Quartz 2d, by adding points to an existing line, gradually over time. I started drawing a new line, In the drawRect method of a UIView, by obtaining the CGContextRef, setting its draw properties, and moving the first point to (0,0).
CGContextRef context= UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context,[UIColor blueColor].CGColor);
CGContextSetLineWidth(context, 2);
CGContextMoveToPoint(context,0,0);
later, in my next drawRect call, i tried extending that line, by again, obtaining the CGContextRef, and adding a new point to it.
GContextRef context= UIGraphicsGetCurrentContext();
CGContextAddLineToPoint(context,x,y);
but it seems that the current CGContextRef doesn't have any record of my previous CGContextMoveToPoint command from the last drawRect call, therefore doesn't have any reference that i already started drawing a line.
Am i doing something wrong here? is there a way refering an already drawn line?
You basically need to treat each call to drawRect as if it was starting from scratch. Even if you are only asked to update a subrect of the view, you should assume that any state associated with the graphics context, such as drawing position and colours, will have been reset. So in your case, you need to keep track of the start position and redraw the whole line each time.
I think the better approach is to animate some thin UIView. Look my answer here.
If you need more than just horizontal line, rotate that view. I think it's better for the performance.