4

My view has a CATiledLayer. The view controller has custom zooming behavior, so on -scrollViewDidEndZooming every tile needs to be redrawn. But, even though -setNeedsDisplay is being called on the layer after every zoom, not all tiles are being redrawn. This is causing the view to sometimes look wrong after a zoom. (Things that should appear in just 1 tile are appearing in multiple places). It often corrects itself after another zoom.

Here's the relevant code. updatedRects is for testing -- it stores the unique rectangles requested to be drawn by -drawLayer.

CanvasView.h:

@interface CanvasView : UIView
@property (nonatomic, retain) NSMutableSet *updatedRects; 
@end

CanvasView.m:

@implementation CanvasView

@synthesize updatedRects; 

+ (Class)layerClass 
{
    return [CATiledLayer class];
}

- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context
{
    CGRect rect = CGContextGetClipBoundingBox(context); 
    [updatedRects addObject:[NSValue valueWithCGRect:rect]]; 

    CGContextSaveGState(context); 
    CGContextTranslateCTM(context, rect.origin.x, rect.origin.y); 
    // ...draw into context...
    CGContextRestoreGState(context); 
}

@end

MyViewController.m:

@implementation MyViewController

- (void)viewDidLoad 
{
    [super viewDidLoad];

    canvasView.updatedRects = [NSMutableSet set]; 
}

- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale
{
    canvasView.transform = CGAffineTransformIdentity; 
    canvasView.frame = CGRectMake(0, 0, canvasView.frame.size.width * scale, canvasView.frame.size.height); 
    [self updateCanvas]; 
}

- (void)updateCanvas
{
    NSLog(@"# rects updated: %d", [canvasView.updatedRects count]); 
    [canvasView.updatedRects removeAllObjects]; 
    canvasView.frame = CGRectMake(canvasView.frame.origin.x, canvasView.frame.origin.y, canvasView.frame.size.width, myHeight); 
    CGFloat tileSize = 256; 
    NSLog(@"next # rects updated will be: %f", [canvasView.layer bounds].size.width / tileSize); 
    [canvasView.layer setNeedsDisplay]; 
}

@end

When testing this, I always scrolled all the way across the view after zooming to make sure every tile was seen. Whenever the view looked wrong, the predicted "next # of rects updated" was greater than the actual "# of rects updated" on the next call to -updateCanvas. What would cause this?

Edit:

Here's a better way to visualize the problem. Each time updateCanvas is called, I've made it change the background color for drawing tiles and record the time it was called -- the color and time are stored in canvasView. On each tile is drawn the tile's rectangle, the tile's index along the x axis, the time (sec.) when the background color was set, and the time (sec.) when the tile was drawn. If everything is working correctly, all tiles should be the same color. Instead, here's what I'm sometimes seeing:

Screenshot of tiles with mismatched colors Screenshot of tiles with mismatched colors

I only seem to see wrong results after zooming out. The problem might be related to the fact that scrollViewDidEndZooming, and therefore updateCanvas, can get called multiple times per zoom-out. (Edit: No-- not called multiple times.)

jlstrecker
  • 4,953
  • 3
  • 46
  • 60
  • 1
    This might be related to a bug in CATiledLayer: if you call "setNeedsDisplay" while it is in the process of drawing a tile, that tile will not be redrawn. – fishinear Aug 02 '12 at 10:07

1 Answers1

10

There is a rather expensive hack that might work for you:

tiledLayer.contents = nil;
[tiledLayer setNeedsDisplayInRect:tiledLayer.bounds];

If I recall correctly, the first line actually turns the tiled layer to an ordinary layer, but the second line turns it back. However, it’ll throw everything in the tiled layer away.

Evadne Wu
  • 3,190
  • 1
  • 25
  • 25
  • thanks, but I don't understand where you put this bit of code. The only place I see where you have access to the tiled layer instance is in drawLayer. If I put the code there my layers don't draw at all. Thanks for your help. – Benoit Jadinon Oct 20 '11 at 07:46
  • You should put it where you now do setNeedsDisplay. The tiled layer is the root layer of your view: canvasView.layer. – fishinear Aug 02 '12 at 10:10