drawRect:/renderInContext: issues
Asked Answered
D

1

0

Seem to be having a bit of an issue trying to draw my view into a context.

My basic setup is, I have a view controller that owns a UIPageViewController in which I load controllers with views that can be drawn on by the users finger. Basically I have a book that can be drawn in.

When the page is turned in the book I save the image by calling

- (UIImage *)wholeImage {
    // Render the layer into an image and return
    UIGraphicsBeginImageContext(self.bounds.size);
    [self.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *wholePageImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return wholePageImage;
}

The return value of this then gets saved to a file. However when I call this save method then line

[self.layer renderInContext:UIGraphicsGetCurrentContext()];

gets hit, which seems to make a call to my drawRect: method, which is overridden as follows,

- (void)drawRect:(CGRect)rect {
    // Draw the current state of the image
    [self.currentImage drawAtPoint:CGPointMake(0.0f, 0.0f)];
    CGPoint midPoint1 = [self midPointOfPoint1:previousPoint1 point2:previousPoint2];
    CGPoint midPoint2 = [self midPointOfPoint1:currentPoint point2:previousPoint1];

    // Get the context
    CGContextRef context = UIGraphicsGetCurrentContext(); 

    // Add the new bit of line to the image
    [self.layer renderInContext:context];
    CGContextMoveToPoint(context, midPoint1.x, midPoint1.y);
    CGContextAddQuadCurveToPoint(context, previousPoint1.x, previousPoint1.y, midPoint2.x, midPoint2.y); 
    CGContextSetLineCap(context, kCGLineCapRound);
    CGContextSetLineWidth(context, self.lineWidth);
    if (self.drawMode == LNDrawViewDrawModeTipex) CGContextSetBlendMode(context, kCGBlendModeClear);
    else CGContextSetBlendMode(context, kCGBlendModeCopy);
    CGContextSetStrokeColorWithColor(context, self.lineColour.CGColor);
    CGContextStrokePath(context);

    // Call super
    [super drawRect:rect];
}

which makes sense, but it seems to get called recursively until eventually I get an EXC_BAD_ACCESS on this line

[self.currentImage drawAtPoint:CGPointMake(0.0f, 0.0f)];

Am at a complete loss as to what is causing this and has been driving me nuts.

If it helps, my call stack starts like this

enter image description here

and then goes on recursively until it finally ends with

enter image description here

Would really appreciate and help and insight that anyone can give!!

EDIT: (Touches moved added)

-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    // Get the touch
    UITouch *touch = [touches anyObject];

    // Get the points
    previousPoint2 = previousPoint1;
    previousPoint1 = [touch previousLocationInView:self];
    currentPoint = [touch locationInView:self];

    // Calculate mid point
    CGPoint mid1 = [self midPointOfPoint1:previousPoint1 point2:previousPoint2]; 
    CGPoint mid2 = [self midPointOfPoint1:currentPoint point2:previousPoint1];

    // Create a path for the last few points
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathMoveToPoint(path, NULL, mid1.x, mid1.y);
    CGPathAddQuadCurveToPoint(path, NULL, previousPoint1.x, previousPoint1.y, mid2.x, mid2.y);
    CGRect pathBounds = CGPathGetBoundingBox(path);
    CGPathRelease(path);

    // Take account of line width
    pathBounds.origin.x -= self.lineWidth * 2.0f;
    pathBounds.origin.y -= self.lineWidth * 2.0f;
    pathBounds.size.width += self.lineWidth * 4.0f;
    pathBounds.size.height += self.lineWidth * 4.0f;

    UIGraphicsBeginImageContext(pathBounds.size);
    [self.layer renderInContext:UIGraphicsGetCurrentContext()];
    self.currentImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    [self setNeedsDisplayInRect:pathBounds];    

}

Dillingham answered 12/4, 2012 at 17:58 Comment(0)
G
0

You must not call [self.layer renderInContext:] in drawRect:, period. As you have figured out, renderInContext: calls drawRect: if it is implemented, which will cause an infinite recursion.

You should render a book as is separately, then use the rendered image to let users draw on top of it. One way to do that is to enclose a self-contained drawing code in the UIGraphicsBeginImageContext()/UIGraphicsEndImageContext() block and use the resulting image in drawRect:. Another way is to use two different views which are not subviews of each other, one drawing an unaltered book, the other drawing user sketches.

EDIT: first of all, you need an original image (book page, whatever). Draw it inside a UIGraphicsBeginImageContext()/UIGraphicsEndImageContext() block without calling [self.layer renderInContext:] and save it in the view's ivar (say, self.currentImage). In touchesMoved:withEvent: call setNeedsDisplay (it will call drawRect: for you) to update the view. Use self.currentImage in drawRect: as the background image.

I hope I'm not too vague.

Gielgud answered 12/4, 2012 at 18:12 Comment(9)
Hi, thanks for your response. Don;t think I'm entirely sure what you mean. Have just added my touches moved code to the question. I do some draw code in a UIGraphicsBeginImageContext()/UIGraphicsEndImageContext() block, but unless I call renderInContext again in the drawRect I seem to get some quite bazaar behaviour..?Dillingham
It seems that drawRect does not always get called from renderInContext as I seem to be able to draw fine onto my view without this problem...!? Am very confused.Dillingham
I think you got your architecture a bit wrong. Basically, you need to do two unrelated things: draw an image and draw something on top of it. Since they are independent, the view which draws the final result should take the original/background image somewhere else, rather than from its own content. The result of drawRect: is set as the view's layer's contents. renderInContext: may or may not call drawRect: each time, depending on a lot of factors outside of your control. It may use the cached image, but it is an implementation detail.Gielgud
I see. You can also render user drawing into an image and then draw the image in drawRect:. When drawing into an image, you can use the result of previous drawing as the background, accumulating drawing operations. drawRect: in this case would just always draw the final image.Gielgud
Sorry just saw your update. The problem is I can't get the original image without calling [self.layer renderInContext:] as the "original image" is actually just what has already been drawn into the layer?Dillingham
Sorry for being such a pain, but how would I go about doing that?Dillingham
Think I have worked out how to do what you said. THANKS!! When you do [self.layer renderInContext:] it puts the whole layer into the context. Is there a way to only render part of the layer into a context??Dillingham
Sure, use Core Graphics clipping paths, see CGContext.hGielgud
Worked it out in the end. Thanks!!Dillingham

© 2022 - 2024 — McMap. All rights reserved.