MKMapView tile images broken in max zoom level

I'm not sure when this started happening, but it seems that the highest MKMapView zoom level never completes loading tiles or is broken. If I zoom out a bit then all tiles appear fine. The project is built with 9.2 SDK for a target 5.1.1 and higher. The 5.1.1 devices I have are not showing this issue (of course they are using Google maps API).

The above image is the emulator running iPhone 4s / iOS 8.1.


(EDIT) not sure why the image is not showing up....

I am having excatly the same issue. I wonder is there any way to override zoom level not go beyond certain level.

Anything this week? Perhaps if you are or you are not having an issue, please let us know...

We are seeing the same problem. We have set the maximumZ for our MKTileOverlay (e.g. 18) but when we zoom greater than that the tiles disappear whereas we expect and users expect that they should just get more zoomed in (rather than trying to load large scale tiles).

The only solution is to manually detect this, and scale a region of the tile from the last available zoom level accordingly in software.


I've filed bugs about it but it's not possible to limit the max zoom level, and Apple Maps does not provide scaling of the tiles for you.


Example image, OSM at zoom level 24 using the XBR scaler.


I think the expected behavior is that the max zoom level is limited. Other scalers are going to look much worse than XBR for basemaps/lineart, and even if MapKit scaled for you, you'd likely just get bilinear.

We started noticing this a few weeks ago. A workaround that fixes the zoomed tiles loading issue was to open the Apple Maps app on the device and zoom in, then go back to our app. This seems to trigger something in the map stack to load the tiles when zooming in.


Has anyone found a programatic way to resolve this issue of the map tiles not loading correctly when zooming?


BTW: I can't reproduce the problem with the iOS simulator.

I do see it in the iOS simulator (iPhone 6s Plus) on the satalite maps, not the standard, although my testers say they have seen it on the standard map as well.

Hi,


I've run into this very issue at work and have created something that works fairly well without setting a global limit.


The MapView delegates that I leverage are:

- mapViewDidFinishRendering

- mapViewRegionDidChange


The premise behind my solution is that since a satellite view renders an area with no data it is always the same thing. This dreaded image (http://imgur.com/cm4ou5g) If we can comfortably rely on that fail case we can use it as a key for determining wha the user is seeing. After the map renders, I take a screenshot of the rendered map bounds and determing an average RGB value. Based off of that RGB value, I assume that the area in question has no data. If that's the case I pop the map back out to the last span that was rendered correctly.


The only global check I have is when it starts to check the map, you can increase or decrease that setting based on your needs. Below is the raw code that will accomplish this and will be putting together a sample project for contribution. Any optimizations you can offer would be appreciated and hope it helps.


@property (assign, nonatomic) BOOL isMaxed;

@property (assign, nonatomic) MKCoordinateSpan lastDelta;



self.lastDelta = MKCoordinateSpanMake(0.006, 0.006);



- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {

if (mapView.mapType != MKMapTypeStandard && self.isMaxed) {

[self checkRegionWithDelta:self.lastDelta.longitudeDelta];

}

}



- (void)checkRegionWithDelta:(float)delta {

if (self.mapView.region.span.longitudeDelta < delta) {

MKCoordinateRegion region = self.mapView.region;

region.span = self.lastDelta;

[self.mapView setRegion:region animated:NO];

} else if (self.mapView.region.span.longitudeDelta > delta) {

self.isMaxed = NO;

}

}





- (void)mapViewDidFinishRenderingMap:(MKMapView *)mapView fullyRendered:(BOOL)fullyRendered {

if (mapView.mapType != MKMapTypeStandard && !self.isMaxed) {

[self checkToProcess:self.lastDelta.longitudeDelta];

}

}



- (void)checkToProcess:(float)delta {

if (self.mapView.region.span.longitudeDelta < delta) {

UIGraphicsBeginImageContext(self.mapView.bounds.size);

[self.mapView.layer renderInContext:UIGraphicsGetCurrentContext()];

UIImage *mapImage = UIGraphicsGetImageFromCurrentImageContext();

[self processImage:mapImage];

}

}



- (void)processImage:(UIImage *)image {

self.mapColor = [self averageColor:image];

const CGFloat* colors = CGColorGetComponents( self.mapColor.CGColor );

[self handleColorCorrection:colors[0]];

}



- (void)handleColorCorrection:(float)redColor {

if (redColor < 0.29) {

self.isMaxed = YES;

[self.mapView setRegion:MKCoordinateRegionMake(self.mapView.centerCoordinate, self.lastDelta) animated:YES];

} else {

self.lastDelta = self.mapView.region.span;

}

}



- (UIColor *)averageColor:(UIImage *)image {

CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

unsigned char rgba[4];

CGContextRef context = CGBitmapContextCreate(rgba, 1, 1, 8, 4, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);

CGContextDrawImage(context, CGRectMake(0, 0, 1, 1), image.CGImage);

CGColorSpaceRelease(colorSpace);

CGContextRelease(context);

if(rgba[3] > 0) {

CGFloat alpha = ((CGFloat)rgba[3])/255.0;

CGFloat multiplier = alpha/255.0;

return [UIColor colorWithRed:((CGFloat)rgba[0])*multiplier

green:((CGFloat)rgba[1])*multiplier

blue:((CGFloat)rgba[2])*multiplier

alpha:alpha];

}

else {

return [UIColor colorWithRed:((CGFloat)rgba[0])/255.0

green:((CGFloat)rgba[1])/255.0

blue:((CGFloat)rgba[2])/255.0

alpha:((CGFloat)rgba[3])/255.0];

}

}

MKMapView tile images broken in max zoom level
 
 
Q