2

Hello i am using MKTileOverlay to present OpenStreetMap tiles in my iOS7 App. Now i'd like to implement the ability to cache these tiles. I saw a post on NSHipster (http://nshipster.com/mktileoverlay-mkmapsnapshotter-mkdirections/) and did it accordingly.

This is my MKTileOverlay subclass:

#import "DETileOverlay.h"

@implementation DETileOverlay

- (void)loadTileAtPath:(MKTileOverlayPath)path
                result:(void (^)(NSData *data, NSError *error))result
{
    if (!result)
    {
        return;
    }

    NSData *cachedData = [self.cache objectForKey:[self URLForTilePath:path]];
    if (cachedData)
    {
        result(cachedData, nil);
    }
    else
    {
        NSURLRequest *request = [NSURLRequest requestWithURL:[self URLForTilePath:path]];
        [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
             result(data, connectionError);
         }];
    }
}

@end

Then i use it like this:

#import "DETileOverlay.h"

@interface DEMapViewController : UIViewController <MKMapViewDelegate> {
}
@property (nonatomic, retain) DETileOverlay *overlay;

-(void)viewDidLoad {
[super viewDidLoad];
    self.overlay = [[DETileOverlay alloc] initWithURLTemplate:@"http://tile.stamen.com/watercolor/{z}/{x}/{y}.jpg"];
        self.overlay.canReplaceMapContent = YES;
        self.overlay.mapView = map;
        [map addOverlay:self.overlay level:MKOverlayLevelAboveLabels];
}

// iOS 7
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id <MKOverlay>)ovl
    {
   MKTileOverlayRenderer *renderer = [[MKTileOverlayRenderer alloc]initWithOverlay:ovl];

        return renderer;
    }


    - (void)          mapView:(MKMapView *)mapView
        didUpdateUserLocation:(MKUserLocation *)userLocation
    {
        MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(userLocation.location.coordinate, 300, 300);

        [map setRegion:region animated:YES];
    }

When i start my app no tiles are loaded. If i don't override the loadTileAtPath in my subclass everything works fine. What am i doing wrong ?

Thanks so much.

dehlen
  • 7,325
  • 4
  • 43
  • 71
  • Did you put a breakpoint into `loadTileAtPath:result:` and check if everything is executing fine? Does the data fetching work? – cweinberger Mar 15 '14 at 19:27
  • i actually did this and it looks like he always goes into the "else" block and tries to load the tiles via NSURLConnection. So the question is why he doesn't load the tiles properly and also why he doesn't cache the tiles... – dehlen Mar 15 '14 at 19:59
  • Follow-up question(s): 1. Is the download of the data successful? 2. Where do you cache the downloaded data? – cweinberger Mar 15 '14 at 20:04
  • i just logged the nsurl from the request so this is totally fine. I just do not understand why the tiles do not get loaded because the url is totally correct. The tiles get saved to cache via ' [self.cache setObject: data forKey: [self URLForTilePath:path]]; ' i forgot to include it in my initial question sorry. – dehlen Mar 15 '14 at 20:09
  • also data is not nil, and connectionError is nil. I can't explain why the tile loading does not work... – dehlen Mar 15 '14 at 20:16
  • I solved it by replacing queue:`self.operationQueue` to `[NSOperationQueue mainQueue]`. Thanks for trying to help anyways ! – dehlen Mar 15 '14 at 20:20

2 Answers2

2

Based on the comments you say you've solved it, but based on your code you never add the tiles to your cache. Without that I don't think you'll get any caching and will always be requesting the tiles anyway. So in your completionHandler you should be adding the resulting tile to your cache like this:

....
} else { 
    NSURLRequest *request = [NSURLRequest requestWithURL:[self URLForTilePath:path]];
    [NSURLConnection sendAsynchronousRequest:request queue:self.operationQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
        // Should inspect the response to see if the request completed successfully!!
        [self.cache setObject:data forKey:[self URLForTilePath:path]];
        result(data, connectionError);
    }];
}
Jeremy Wiebe
  • 3,894
  • 22
  • 31
0

I don't see it in your code, but be sure to initialize your cache and operation queue. Using your code exactly does not work. When I initialize the MKTileOverlay, I set its cache and operation queue. Then it all works.

Kevin
  • 436
  • 4
  • 12