views:

977

answers:

1

in my application I use have the following views hierarchy:

UIView
----UIScrollView
--------TiledView (UIView subclass, uses CATiledLayer for drawing)
----OverlayView (UIView subclass)

In short - TiledView displays large tiled image I also apply custom rotation to that view:

tiledView.transform = CGAffineTransformMakeRotation(angle);

Drawing method for TiledView:

- (void)drawRect:(CGRect)rect {
    // Drawing code
    CGContextRef context = UIGraphicsGetCurrentContext();  
    ...
  NSString *fileName = [NSString stringWithFormat:@"tile_%d_%d.jpg", y + 1, x + 1];
    UIImage *image = [UIImage imageNamed:fileName];
    [image drawInRect:rect];
 }

UIScrollView allows both scrolling and zooming of its contents.
Overlay view lays over UIScrollView, has transparent background and performs some custom drawing. I use separate view to make sure that line width and font size are not affected by zoom scale in scroll view.

Drawing method for OverlayView:

- (void)drawRect:(CGRect)rect {
    // Drawing code
    [super drawRect:rect];

    // Custom drawing code
    CGContextRef context = UIGraphicsGetCurrentContext();

    CGContextSetRGBStrokeColor(context, 1.0, 1.0, 0, 1);
   CGContextSetLineWidth(context, lineWidth);

    CGContextBeginPath(context);
    CGContextMoveToPoint(context, (int)startPoint.x,(int) startPoint.y);

    if (usesMidPoint)
        CGContextAddLineToPoint(context, (int)midPoint.x, (int)midPoint.y); 
    CGContextAddLineToPoint(context, endPoint.x, endPoint.y);

    CGContextStrokePath(context);
}

Initially everything works ok, but after some playing with the view (e.g. scrolling it to and fro etc) it crashes on some random line in one of the drawing functions. As an example app crashes on line:

CGContextSetRGBStrokeColor(context, 1.0, 1.0, 0, 1); 

with stack:

#0  0x00503088 in CGColorEqualToColor
#1  0x00505430 in CGGStateSetStrokeColor
#2  0x005053b6 in setStrokeColorWithComponents
#3  0x0056150f in CGContextSetRGBStrokeColor
#4  0x000764ab in -[GADrawView drawRect:] at GADrawView.m:38
#5  0x016a7a78 in -[UIView(CALayerDelegate) drawLayer:inContext:]
#6  0x0077c007 in -[CALayer drawInContext:]

Am I missing any synchronization required between several graphics contexts? Or may be there's better way to do what I'm trying?

+3  A: 

Found solution to the problem. As described in Apple's technical note CATiledLayer uses separate thread to fetch the contents for its tiles:

The CATiledLayer implements its drawing by using a background thread to fetch the contents of each tile, however the drawing functions provided by UIKit rely on a global context stack, and as such when a CATiledLayer starts rendering, using any of UIKit's drawing functions can result in a race condition.

So the solution is to move all drawing code from -drawRect: method to -drawLayer:inContext: and draw using only Core Graphics functions. Drawing with CoreGraphics also requires some transformations between coordinates systems - this post from apple forums helped with them (see drawLayer: method implementation).

So the correct drawing code for TiledView:

- (void)drawRect:(CGRect)rect {
    //Still need to have this method implemented even if its empty!
}

-(void)drawLayer:(CALayer*)layer inContext:(CGContextRef)ctx
{
    // Do all your drawing here. Do not use UIGraphics to do any drawing, use Core Graphics instead.
    // convert the CA coordinate system to the iPhone coordinate system
    CGContextTranslateCTM(ctx, 0.0f, 0.0f);
    CGContextScaleCTM(ctx, 1.0f, -1.0f);

    CGRect box = CGContextGetClipBoundingBox(ctx);

    // invert the Y-coord to translate between CA coords and iPhone coords
    CGPoint pixelTopLeft = CGPointApplyAffineTransform(box.origin, CGAffineTransformMakeScale(1.0f, -1.0f));

    NSString *tileUrlString = [self urlForPoint:pixelTopLeft];

    UIImage *image = [UIImage imageNamed:tileUrlString];
    CGContextDrawImage(ctx, box, [image CGImage]);
}
Vladimir