UIView: how to do non-destructive drawing?
Asked Answered
D

4

18

My original question:

I'm creating a simple drawing application and need to be able to draw over existing, previously drawn content in my drawRect. What is the proper way to draw on top of existing content without entirely replacing it?

Based on answers received here and elsewhere, here is the deal.

  1. You should be prepared to redraw the entire rectangle whenever drawRect is called.

  2. You cannot prevent the contents from being erased by doing the following:

    [self setClearsContextBeforeDrawing: NO];

    This is merely a hint to the graphics engine that there is no point in having it pre-clear the view for you, since you will likely need to re-draw the whole area anyway. It may prevent your view from being automatically erased, but you cannot depend on it.

  3. To draw on top of your view without erasing, do your drawing to an off-screen bitmap context (which is never cleared by the system.) Then in your drawRect, copy from this off-screen buffer to the view.

Example:

- (id) initWithCoder: (NSCoder*) coder {    
     if (self = [super initWithCoder: coder]) {
         self.backgroundColor = [UIColor clearColor];
         CGSize size = self.frame.size;
         drawingContext = [self createDrawingBufferContext: size];
     }

     return self;
 }

- (CGContextRef) createOffscreenContext: (CGSize) size  {
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate(NULL, size.width, size.height, 8, size.width*4, colorSpace, kCGImageAlphaPremultipliedLast);
    CGColorSpaceRelease(colorSpace);

    CGContextTranslateCTM(context, 0, size.height);
    CGContextScaleCTM(context, 1.0, -1.0);
    return context;
}


- (void)drawRect:(CGRect) rect {    
    UIGraphicsPushContext(drawingContext);
    CGImageRef cgImage = CGBitmapContextCreateImage(drawingContext); 
    UIImage *uiImage = [[UIImage alloc] initWithCGImage:cgImage];
    UIGraphicsPopContext();
    CGImageRelease(cgImage);
    [uiImage drawInRect: rect];
    [uiImage release];
 }

TODO: can anyone optimize the drawRect so that only the (usually tiny) modified rectangle region is used for the copy?

Diarrhea answered 14/5, 2009 at 20:40 Comment(0)
B
5

It is fairly common to draw everything in an offscreen image, and simply display this image when drawing the screen. You can read: Creating a Bitmap Graphics Context.

Behlau answered 14/5, 2009 at 20:46 Comment(5)
Hmm, that reference seems to be C code. When I tried adding it to my Objective-C class the simulator crashes (along with the debugger, sadly).Diarrhea
As far as I can tell, this is Quartz, which is supported on the iPhone. You've probably done something wrong... Care to show us what you've done?Behlau
Sure. My drawRect() calls the "flag drawing" code from the example cited. I have copy/pasted it to: pastebin.com/m68230dcd Thanks for your time.Diarrhea
I think you misunderstood what I meant. You should create a single layer context in which you would render your primitives (using Quartz functions). From what I've seen, your creating a new context for every shape.Behlau
I got this working, thanks. It's still a little slow copying from UIView -> UIImage -> UIView. I suspect I can speed it up if I can figure out how to copy only the (small) sub-rectangle that actually changed, rather than copying the entire view. Thanks.Diarrhea
M
1

On optimizing drawRect

Try this:

- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGImageRef cgImage = CGBitmapContextCreateImage(drawingContext);
    CGContextClipToRect(context, rect);
    CGContextDrawImage(context, CGRectMake(0, 0, self.frame.size.width, self.frame.size.height), cgImage);
    CGImageRelease(cgImage);
}

After that you should also comment these lines in your code:

//CGContextTranslateCTM(context, 0, size.height);
//CGContextScaleCTM(context, 1.0, -1.0);

Also created a separate question to be sure that it's the optimal way.

Mcconaghy answered 14/6, 2010 at 22:58 Comment(0)
T
0

This seems like a better method than I've been using. That is, in a touches event I make a copy of the view about to be updated. Then in drawRect I take that image and draw it to the view and make my other view changes at the same time.

But this seems inefficient but the only way I figured out how to do it.

Tetherball answered 14/11, 2009 at 20:38 Comment(0)
A
0

This prevents your view from being erased before drawRect is done:

[self.layer setNeedsDisplay];

Also, I find it is better to do all the drawing in the drawRect method (unless you have a good reason not to). Drawing offscreen and transferring takes more time and adds more complexity then simply drawing everything once.

Arvo answered 12/7, 2012 at 9:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.