3

I'm trying to find a way to draw lines on a view without redrawing all the context.

Here is my drawing methods :

-(void)drawInContext:(CGContextRef)context {
    for (int i = 0; i < self.drawings.count; ++i) {
        Drawing* drawing = [self.drawings objectAtIndex:i];

        CGContextSetStrokeColorWithColor(context, drawing.colorTrait.CGColor);
        CGContextSetLineWidth(context, 1.0);

        CGContextMoveToPoint(context, [[drawing.points objectAtIndex:0] CGPointValue].x * self.zoomScale, [[drawing.points objectAtIndex:] CGPointValue].y * self.zoomScale);

        for (int i = 1; i < drawing.points.count; i++) {
            CGContextAddLineToPoint(context, [[drawing.points objectAtIndex:i] CGPointValue].x * self.zoomScale, [[drawing.points objectAtIndex:i] CGPointValue].y * self.zoomScale);
        }

        CGContextStrokePath(context);
    }
}

-(void)drawRect:(CGRect)rect {
    if (isRedrawing) {
        [self drawInContext:UIGraphicsGetCurrentContext()];
        isRedrawing = NO;
    }

    [[UIColor redColor] set];
    [currentPath stroke];
}

But when I call setNeedsDisplay in my touches methods, the view is entirely cleared. Is there a way to make my method work ?

Seb
  • 545
  • 9
  • 29

1 Answers1

3

For better or for worse I use layers:

- (void)drawRect:(CGRect)rect {

    CGContextRef context = UIGraphicsGetCurrentContext();

    if (first) {

        // Wait for the first call to get a valid context

        first = NO;

        // Then create a CGContextRef (offlineContext_1) and matching CGLayer (layer_1)

        layer_1 = CGLayerCreateWithContext (context,self.bounds.size, NULL);    
        offlineContext_1 = CGLayerGetContext (layer_1);

        // If you have any pending graphics to draw, draw them now on offlineContext_1

    }

    // Normally graphics calls are made here, but the use of an "offline" context means the graphics calls can be made any time.

    CGContextSaveGState(context);

    // Write whatever is in offlineContext_1 to the UIView's context 

    CGContextDrawLayerAtPoint (context, CGPointZero, layer_1);
    CGContextRestoreGState(context);

}

The idea is that though the UIView's context is always cleared, the offline context associated with the Layer is not. You can keep accumulating graphical actions and not have them cleared by drawRect.

EDIT: So you've pointed out one difference between your problem and the one I solved. I didn't need to draw anything until after the first display whereas you want to draw something before the first display. From what I remember, you can create a layer at any time at any size and resolution. I found it was easiest to wait for a valid context (with the right settings for the current device) to be passed to me and then create a new context/layer based on that context. See the inside of the "if (first)" block.

You can do the same thing, but you'll also have to cache the lines etc that you get from the server until such time as the first drawRect: is called. You can then create the offline context with the given context, draw the lines etc. that you got from the server to the offline context and then draw the layer as shown below. So the only thing you'd add to the method is a call inside the "if (first)" loop to draw pending server-sources graphics before continuing (see added comment in source).

Chris Gerken
  • 16,221
  • 6
  • 44
  • 59
  • Where can I call the drawInContext() method in your drawRect ? – Seb Dec 13 '12 at 20:47
  • @Seb The idea is that you have a set of gestures and other user actions that draw on the offlineContext as they happen. In addition, those actions call **setNeedsDisplay: true** so the UIView redraws itself. When it redraws, it clears itself, but then copies the layer containing all the graphic history. – Chris Gerken Dec 13 '12 at 20:51
  • I think I understood the why of this choice but I'm not sure to understand how to use it with my app. I have lines that a server send me and I want to draw them on the view at the first launch. After that, I want to draw new lines without redraw everything. Actually, with your method (I changed nothing), none of my drawing are displayed and the lines are erased after I create a new one. Am I clear ? Can you explain me with more precisions ? Sorry, I'm a beginner... :/ – Seb Dec 24 '12 at 16:28
  • How the graphics of the offlineContext_1 can be displayed on the drawing view ? – Seb Dec 24 '12 at 16:36
  • @Seb the call to CGContextDrawLayerAtPoint() is where the offline context gets drawn. There's an underlying relationship between the offline context (my term) and the layer (that's from the call to CGLayerCreateWithContext()). You draw on that context all you want and then use the CGContextDrawLayerAtPoint() call to draw the layer to the actual context. – Chris Gerken Dec 24 '12 at 21:26
  • @Seb just edited my answer concerning graphics to be drawn before the first display. There may be another way to do it, but it would be nice (IMHO) to exploit the technique that lets you keep your drawing history across screen updates. – Chris Gerken Dec 24 '12 at 22:05
  • Ok I tried your method, it works but I have 2 problems : - The lines doesn't get the same quality (they are a bit pixelized) than the original method, is it normal ? Is there a way to solve that ? - When I have a big zoom, it still takes a long time to draw like before I use this method. The aim of the method I want to implement is to make the drawing quicker. – Seb Dec 26 '12 at 13:01
  • I accepted your answer but if you can answer to my last comment, it will be fantastic ! – Seb Jan 09 '13 at 09:14
  • You may need to redraw the entire context if you zoom in enough so that lines get pixelized. When you do that, though, you only have to redraw graphics in the rectangle passed to drawRect:. – Chris Gerken Jan 16 '13 at 17:01