setNeedsDisplayInMapRect не запускает новый drawMapRect: вызов
Я использую кастом MKOverlay/MKOverlayView
чтобы полностью покрыть базовую карту Google моими собственными плитками, которые загружаются асинхронно. Я следую примеру запроса незагруженных плиток, когда получаю canDrawMapRect:zoomScale:
вызвать мой оверлейный вид (и в этом случае вернуть FALSE), затем вызвать setNeedsDisplayInMapRect:zoomScale:
когда плитка доступна.
Это все в целом работает, и, кажется, отлично работает в симуляторе.
Однако на устройстве я иногда получаю "дыру" в оверлее - недостающую плитку.
Я вижу, что плитка запрошена, и что запрос завершен. Я вижу, что я звоню setNeedsDisplayInMapRect:zoomScale:
и что я передаю оригинал MKMapRect
а также MKZoomScale
которые были предоставлены в canDrawMapRect:zoomScale:
, Но я также вижу, что оверлей никогда не просят перерисовать эту плитку (ни canDrawMapRect:zoomScale:
ни drawMapRect:zoomScale:inContext:
когда-либо снова призывал к этой плитке).
Мне нужно понять, почему это происходит и как это исправить.
Вот соответствующий код из моего подкласса MKOverlayView:
- (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];
}
РЕДАКТИРОВАТЬ: Я предполагаю, что это, вероятно, проблема.
2 ответа
У меня была проблема, очень похожая на описанную здесь. В моем случае я не смог воспроизвести желаемое поведение (описано в http://developer.apple.com/library/ios/documentation/MapKit/Reference/MKOverlayView_class/Reference/Reference.html:), даже имея самый простой код:
- (BOOL)canDrawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale {
NSLog(@"This should trace forever");
[self setNeedsDisplayInMapRect:mapRect zoomScale:zoomScale];
return NO;
}
или ближе к моему исходному коду:
- (BOOL)canDrawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale {
NSLog(@"This should trace forever");
[SomeAsynchronousRequestWithCompletionHandler:^{
[self setNeedsDisplayInMapRect:mapRect zoomScale:zoomScale];
}];
return NO;
}
В обоих случаях setNeedsDisplayInMapRect:zoomScale: никогда не вызывался ни разу.
Ситуация изменилась, когда я начал выполнять setNeedsDisplayInMapRect:zoomScale: внутри dispatch_async, отправляемого в ту же очередь, в которой работает canDrawMapRect, например:
- (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;
}
или с включенной асинхронной работой:
- (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;
}
Использование dispatch_async - я вижу, что строка "Это должно быть навсегда" отслеживается бесконечно. Моя первоначальная проблема также полностью исчезла.
ПОСЛЕДНЕЕ ОБНОВЛЕНИЕ: В настоящее время я использую dispatch_get_main_queue() для вызова 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;
}
Ответ выше не работал для меня. Из распечатки NSLog, которую я использовал, я увидел, что используется другой поток, несмотря на то, что он захватил dispatch_get_current_queue() в canDrawMapRect и сохранил его для дальнейшего использования. Это имело место в iPad 4.3 Simulator, по крайней мере, я не пытался на устройстве.
Мое решение было менее удовлетворительным и более подверженным ошибкам решением ожидания х времени перед вызовом.
-(void)setNeedsDisplay:(WebRequest*)webRequest
{
[webRequest retain];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.25 * NSEC_PER_SEC), dispatch_get_main_queue(),
^{
[webRequest autorelease];
[delegate setNeedsDisplayInMapRect:webRequest.request.requestedMapRect
zoomScale:webRequest.request.requestedScale];
});
}