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];
}
The answer above did not work for me. From the NSLog printout I used I could see that a different thread was being used for despite grabbing the dispatch_get_current_queue() in canDrawMapRect and storing it for later use. This was the case in the iPad 4.3 Simulator at least, I did not attempt on the device.
My solution was less satisfactory and more error prone solution of wait x time before calling.
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:
or closer to my original code:
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:
or with asynchronous job included:
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