Search code examples
iosmkmapviewmapkitoffline-cachingmktileoverlay

Offline Map in iOS


I have a big need to do an offline map for my app, as it is made mostly for Thailand, where internet connection is often hard to come by. I am using OpenStreetMap right now for my MKTileOverlay but am having issues implementing it for offline use. I have found a tutorial that says to subclass MKTileOverlay. So, in my ViewController where the map is I have:

 -(void) viewWillAppear:(BOOL)animated {
CLLocationCoordinate2D coord = {.latitude =  15.8700320, .longitude =  100.9925410};
MKCoordinateSpan span = {.latitudeDelta =  3, .longitudeDelta =  3};
MKCoordinateRegion region = {coord, span};
[mapView setRegion:region];
}
- (void)viewDidLoad {
    [super viewDidLoad];
    self.title = @"Map";
    NSString *template = @"http://tile.openstreetmap.org/{z}/{x}/{y}.png";
    self.overlay = [[XXTileOverlay alloc] initWithURLTemplate:template];
    self.overlay.canReplaceMapContent = YES;
    [mapView addOverlay:self.overlay level:MKOverlayLevelAboveLabels];


}
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id)overlay {

        return [[MKTileOverlayRenderer alloc] initWithTileOverlay:overlay];

}

In my subclass of MKTileOverlay, I have:

- (NSURL *)URLForTilePath:(MKTileOverlayPath)path {
    return [NSURL URLWithString:[NSString stringWithFormat:@"http://tile.openstreetmap.org/{%ld}/{%ld}/{%ld}.png", (long)path.z, (long)path.x, (long)path.y]];
}

- (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:self.operationQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
            result(data, connectionError);
        }];
    }
}

The issue is that NOTHING gets loaded at all, unless I comment out the code in the subclass. Where am I messing up?


Solution

  • I am also interested in loading the map in offline mode.

    1. First of all I downloaded the tiles into my local system (computer) with the help of gmapcatcher.

      You can get some information about the gmapcatcher here. And you can download this application here.

      One more important thing if you want to download the files check offline and there are many ways that can save the files. For this scenario select OSM in setting window (located to the left side where you will specify the path to download in the setting window)

    2. Simply add that created tiles folder to the project.

    3. And I don' know objective-c so I am implementing in the swift and I posted that code only.

    Here is the code

    import UIKit
    import MapKit
    class ViewController: UIViewController, MKMapViewDelegate {
        @IBOutlet weak var mapView: MKMapView!
    
        override func viewDidLoad() {
            super.viewDidLoad()
            self.mapView.delegate = self
    
            let baseURL = NSBundle.mainBundle().bundleURL.absoluteString
            let urlTemplate = baseURL.stringByAppendingString("OSM_sat_tiles/{z}/{x}/{y}.png")
            //OSM_sat_tiles is the folder name which has the tiles.
            let layer = MKTileOverlay(URLTemplate: urlTemplate)
            layer.canReplaceMapContent = true
            self.mapView.addOverlay(layer)
        }
    
        func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer {
            if overlay is MKTileOverlay {
                let renderer = MKTileOverlayRenderer(overlay:overlay)
                renderer.alpha = 0.8
                return renderer
            }
            return MKTileOverlayRenderer(overlay: overlay)
        }
    }
    

    I will go brief on the code.

    In the viewDidLoad delegate method I am creating MKTileOverlay by giving path of the tiles folder. Since I have placed in the project it will be in the bundle.

    And in the rendererForOverlay delegate method I am returning the MKTileOverlayRenderer.