Overriding drawRect:(CGRect)rect in Objective-C (iOS)? - ios

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.

Related

Using drawRect to re-draw only part of the view

drawRect passes in a rectangle which represents the area of your view to re-draw. The Apple Docs say:
Your implementation of this method should redraw the specified area of
the view as quickly as possible
What is not clear to me is how you should actually proceed to use this rectangle that is passed in. Suppose your view is made up of a single Bezier Curve and drawRect asks you draw only part of that curve. Well, even this simple example is non-trivial since you must break up the curve to find the part that lies only within the rectangle.
In nearly all the example code I see for drawRect the actual argument to the method is entirely ignored. So perhaps the typical case is to just ignore this argument? What happens if you draw based on the entire view's bounds in mind, but you instead only receives a small portion of that view to this method?

Resize UIView so that whatever I draw is visible - ObjectiveC

I have a UIView. I am drawing a line inside the UIView programmativally. But when the line goes outside the UIView, the part of the line which goes out, is invisible. How can I resize the UIView so that whatever I draw inside the drawRect method is visible?
you can change the frame of view. If your line is horizontal then give width to view else increase height of view.
view.frame = CGRectMake(view.frame.origine.x, view.frame.origine.y,view.frame.size.width,lengthOfLine );
If the curve you are drawing is a subview, then you can make use of sizeToFit method. This will make the view's frame enclose the curve(and all subviews, for that matter). Then you can reposition and scale the view's frame to make it fit in the window.
You have mentioned in a comment that you are actually drawing a curve. From what I can tell, you will need to calculate the curve's bounding box yourself.
Based on the bounding box, update the UIView's bounds property (as Durgaprasad suggested). This also resizes the underlying CALayer, which also gives its underlying Core Graphics rendering context a larger bitmap.
Without knowing more about your curve, it's hard to help, apart from linking to some very generic discussion on quadratic Beziers.
You may want to update your question with a minimal implementation of -drawRect: that will allow someone to reproduce your issue.

On iOS, can something be drawn without having it done in drawRect?

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.

On iOS, if drawRect doesn't use the rect that was passed in, does it matter if [self setNeedsDisplayInRect:rect] passes in a rect?

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.

Does drawRect: automatically check whether something is within the bounds of the CGRect passed to it?

I'm wondering whether I need to check if something is within the bounds of the CGRect passed to drawRect:, or if drawRect: automatically handles that for me.
For example, assume that I have 10 UIBezierPaths on the screen. Each curve is in an NSMutableArray named curves. Every time drawRect: is called, it loops through this array and draws the curves it finds there. If the use moves one curve, I find its containing CGRect and call [self setNeedsDisplayInRect:containingRect]. In my drawRect: implementation, do I need to personally check whether each of the UIBezierPaths falls within the CGRect passed to drawRect: (using CGRectIntersectsRect), or is that handled automatically?
This falls into a class of optimizations you'll have to make yourself if you think it's necessary after profiling.
UIKit isn't that smart unfortunately. Though it would probably be too slow if it was!

Resources