Search code examples
iosmkmapviewmapkitmkoverlay

setNeedsDisplayInMapRect doesn't trigger new drawMapRect: call


I'm using a custom MKOverlay/MKOverlayView to completely cover the Google basemap with my own tiles, which are loaded asynchronously. I follow the pattern of requesting unloaded tiles when I receive a canDrawMapRect:zoomScale: call to my overlay view (and returning FALSE in that case), then calling setNeedsDisplayInMapRect:zoomScale: once the tile is available.

This all generally works, and appears to work perfectly in the simulator.

However, on the device I sometimes get a 'hole' in the overlay - a missing tile.

I can see that the tile is requested, and that the request completes. I can see that I call setNeedsDisplayInMapRect:zoomScale:, and that I am passing the original MKMapRect and MKZoomScale which were provided in canDrawMapRect:zoomScale:. But I can also see that the overlay is never asked to redraw that tile (neither canDrawMapRect:zoomScale: nor drawMapRect:zoomScale:inContext: is ever again called for that tile).

I need to understand why this is happening and how to correct it.

Here's the relevant code from my MKOverlayView subclass:

- (BOOL) canDrawMapRect: (MKMapRect) mapRect zoomScale: (MKZoomScale) zoomScale 
{
    NSUInteger zoomLevel = [self zoomLevelForZoomScale:zoomScale];
    CGPoint mercatorPoint = [self mercatorTileOriginForMapRect:mapRect];
    NSUInteger tilex = floor(mercatorPoint.x * [self worldTileWidthForZoomLevel:zoomLevel]);
    NSUInteger tiley = floor(mercatorPoint.y * [self worldTileWidthForZoomLevel:zoomLevel]);

    NSURL* tileUrl = [self tileURLForZoomLevel: zoomLevel tileX: tilex tileY: tiley];

    ASIHTTPRequest* tileRequest = [ASIHTTPRequest requestWithURL: tileUrl];
    tileRequest.downloadCache = [ASIDownloadCache sharedCache];
    [tileRequest setCacheStoragePolicy:ASICachePermanentlyCacheStoragePolicy];

    if ( NO == [[ASIDownloadCache sharedCache] isCachedDataCurrentForRequest: tileRequest] )
    {
        [tileRequest setCachePolicy: ASIAskServerIfModifiedWhenStaleCachePolicy];
        tileRequest.delegate = self;
        tileRequest.userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
                                [NSValue value: &mapRect withObjCType: @encode( MKMapRect )],       @"mapRect",
                                [NSValue value: &zoomScale withObjCType: @encode( MKZoomScale )],   @"zoomScale",
                                [NSNumber numberWithInt: tilex], @"tilex",
                                [NSNumber numberWithInt: tiley], @"tiley",
                                nil];

        [_tileRequestStack addOperation: tileRequest];

        NSLog( @"canDrawMapRect: %d, %d - REQUESTING", tilex, tiley );

        return NO;
    }

    NSLog( @"canDrawMapRect: %d, %d - READY", tilex, tiley );

    return YES;
}

- (void) drawMapRect: (MKMapRect) mapRect zoomScale: (MKZoomScale) zoomScale inContext: (CGContextRef) context 
{
    NSUInteger zoomLevel = [self zoomLevelForZoomScale:zoomScale];

    CGPoint mercatorPoint = [self mercatorTileOriginForMapRect:mapRect];

    NSUInteger tilex = floor(mercatorPoint.x * [self worldTileWidthForZoomLevel:zoomLevel]);
    NSUInteger tiley = floor(mercatorPoint.y * [self worldTileWidthForZoomLevel:zoomLevel]);

    NSLog( @"drawMapRect:  %d, %d", tilex, tiley );

    NSURL* tileUrl = [self tileURLForZoomLevel: zoomLevel tileX: tilex tileY: tiley];
    NSData* tileData = [[ASIDownloadCache sharedCache] cachedResponseDataForURL: tileUrl];

    UIGraphicsPushContext(context);

    if ( tileData != nil )
    {
        UIImage* img = [UIImage imageWithData: tileData];

        if ( img != nil )
        {
            [img drawInRect: [self rectForMapRect: mapRect] blendMode: kCGBlendModeNormal alpha: 1.0 ];
        }
        else 
        {
            NSLog( @"oops - no image" );
        }

        CGSize s = CGContextConvertSizeToUserSpace( context, CGSizeMake( 40, 1 ));

        UIFont* f = [UIFont systemFontOfSize: s.width];

        [[UIColor blackColor] setFill];

        [[NSString stringWithFormat: @"%d,%d", tilex, tiley] drawInRect: [self rectForMapRect: mapRect] withFont: f];
    }

    UIGraphicsPopContext();
}


- (void) requestFinished: (ASIHTTPRequest *) tileRequest
{
    NSValue* mapRectValue =  [tileRequest.userInfo objectForKey: @"mapRect"];
    MKMapRect mapRect;  [mapRectValue getValue: &mapRect];

    NSValue *zoomScaleValue = [tileRequest.userInfo objectForKey:@"zoomScale"];
    MKZoomScale zoomScale; [zoomScaleValue getValue: &zoomScale];

    NSLog( @"requestFinished: %d, %d, %lf", 
          [[tileRequest.userInfo objectForKey:@"tilex"] intValue], 
          [[tileRequest.userInfo objectForKey:@"tiley"] intValue], 
          zoomScale  );

    [self setNeedsDisplayInMapRect: mapRect zoomScale: zoomScale];
}

EDIT: I'm guessing that this is likely the issue.


Solution

  • I had an issue very similar to the one described here. In my case I couldn't reproduce the desired behaviour (described in http://developer.apple.com/library/ios/documentation/MapKit/Reference/MKOverlayView_class/Reference/Reference.html#//apple_ref/occ/instm/MKOverlayView/setNeedsDisplayInMapRect:zoomScale:) even having the most simple code possible:

    - (BOOL)canDrawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale {
        NSLog(@"This should trace forever");
    
        [self setNeedsDisplayInMapRect:mapRect zoomScale:zoomScale];
        return NO;
    }
    

    or closer to my original code:

    - (BOOL)canDrawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale {
        NSLog(@"This should trace forever");
    
        [SomeAsynchronousRequestWithCompletionHandler:^{
            [self setNeedsDisplayInMapRect:mapRect zoomScale:zoomScale];
        }];
        return NO;
    }
    

    In both cases setNeedsDisplayInMapRect:zoomScale: has never been called even once.

    The situation changed, when I began running setNeedsDisplayInMapRect:zoomScale: inside a dispatch_async dispatched to the same queue that canDrawMapRect runs on, like:

    - (BOOL)canDrawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale {
    
        dispatch_queue_t queue = dispatch_get_current_queue();
    
        NSLog(@"This should trace forever");
    
        dispatch_async(queue, ^{
            [self setNeedsDisplayInMapRect:mapRect zoomScale:zoomScale];
        });
    
        return NO;
    }
    

    or with asynchronous job included:

    - (BOOL)canDrawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale {
        NSLog(@"This should trace forever");
    
        dispatch_queue_t queue = dispatch_get_current_queue();
    
        [SomeAsynchronousRequestWithCompletionHandler:^{
            dispatch_async(queue, ^{
                [self setNeedsDisplayInMapRect:mapRect zoomScale:zoomScale];
            });
        }];
        return NO;
    }
    

    Using dispatch_async - I can see "This should trace forever" string being traced endlessly. My original problem is also disappeared completely.

    LATER UPDATE: Currently, I use dispatch_get_main_queue() to call setNeedsDisplayInMapRect:zoomScale: like

    - (BOOL)canDrawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale {
        NSLog(@"This should trace forever");
    
        [SomeAsynchronousRequestWithCompletionHandler:^{
            dispatch_async(dispatch_get_main_queue(), ^{
                [self setNeedsDisplayInMapRect:mapRect zoomScale:zoomScale];
            });
        }];
        return NO;
    }