Drawing a UIView
The most flexible way to draw a UIView is to draw it yourself. Actually, you don’t draw a UIView; you subclass UIView and endow the subclass with the ability to draw itself.When a UIView needs drawing, its drawRect: method is called. Overriding that method is your chance to draw. At the time that drawRect: is called, the current graphics context has already been set to the view. You can use Core Graphics functions or UIKit convenience methods to draw into that context.
You should never call drawRect: yourself. If a view needs updating and you want its drawRect: called, send the view the setNeedsDisplay message. This will cause drawRect: to be called at the next proper moment.
If you subclass a built-in UIView subclass, don’t override drawRect: unless you are assured that this is legal. For example, it is not legal to override drawRect: in a subclass of UIImageView; you cannot combine your drawing with that of the UIImageView.
We’ll have a UIView subclass called MyView, in which we’ll do all our drawing. How this class gets instantiated, and how the instance gets into our view hierarchy, isn’t important. Here, I’ll do it in code as the app launches:
MyView* mv = [[MyView alloc] initWithFrame:CGRectMake(0, 0, self.window.bounds.size.width - 50, 150)];
mv.center = self.window.center;
[self.window addSubview: mv];
mv.opaque = NO;
[mv release];
The drawing action all takes place in MyView’s drawRect: method. At the time drawRect: is called, we are guaranteed that the current Core Graphics context is MyView itself, so we can obtain this if we need to with UIGraphicsGetCurrentContext, and then we can do here whatever we did in our earlier examples of drawing in a context. For example, we can draw two halves of Mars, one at each end of the view:
- (void)drawRect:(CGRect)rect {
CGRect b = self.bounds;
UIImage* mars = [UIImage imageNamed:@"Mars.png"];
CGSize sz = [mars size];
CGImageRef marsCG = [mars CGImage];
CGSize szCG = CGSizeMake(CGImageGetWidth(marsCG), CGImageGetHeight(marsCG));
CGImageRef marsLeft = CGImageCreateWithImageInRect(marsCG,
CGRectMake(0,0,szCG.width/2.0,szCG.height));
CGImageRef marsRight = CGImageCreateWithImageInRect(marsCG,
CGRectMake(szCG.width/2.0,0,szCG.width/2.0,szCG.height));
CGContextRef con = UIGraphicsGetCurrentContext();
CGContextDrawImage(con, CGRectMake(0,0,sz.width/2.0,sz.height), flip(marsLeft));
CGContextDrawImage(con, CGRectMake(b.size.width-sz.width/2.0, 0, sz.width/2.0,
sz.height), flip(marsRight));
CGImageRelease(marsLeft); CGImageRelease(marsRight);
}
There is no need to call super, because the superclass here is UIView, whose drawRect: does nothing.
The need to draw in real time, on demand, surprises some beginners, who worry that drawing may be a time-consuming operation. Equally surprising is the need to draw repeatedly; even if the drawing has not changed, you may be called upon to perform the same drawing again. Where drawing is extensive and can be compartmentalized into sections, you may be able to gain some efficiency by paying attention to the rect parameter passed into drawRect:. It designates the region of the view’s bounds that needs refreshing. The system knows this either because this is the area that has just been exposed by the removal of some covering view or because you called setNeedsDisplayInRect:, specifying it. Thus, you could call setNeedsDisplayInRect: to tell your
drawRect: to redraw a subregion of the view; the rest of the view will be left alone.
In general, however, you should not optimize prematurely. What looks like a lengthy drawing operation may be extremely fast. And the iOS drawing system is efficient; it doesn’t call drawRect: unless it has to (or is told to, through a call to setNeedsDisplay), and once a view has drawn itself, the result is cached so that the cached drawing can be reused instead of repeating the drawing operation from scratch.
Making a View’s Background Transparent
If a view’s backgroundColor is nil (the default when creating a UIView in code) and its opaque is YES (ditto), it will be drawn with a black background; therefore, to make such a view’s background transparent, you must set its opaque to NO. This problem doesn’t arise with a view instantiated from a nib, because you can’t assign a view a nil backgroundColor in the nib; it always has some background color, even if it is what the nib calls Clear Color ([UIColor clearColor], or transparent black). Thus, assigning [UIColor clearColor] as a code-created view’s backgroundColor has the same apparent
effect, and you may encounter code that does this. But Apple warns that you should still set a transparent view’s opaque to NO, or incorrect drawing may occur.