viewPusher
viewPusher

Reputation: 211

Offline MapKit solution for iOS

Quick question for you. I have an app that I'm working on that will require the use of maps, but will not have a network connection. I have seen others ask similar questions about this, but my requirements are a slight bit different, so I wanted to post a new question.

Here are my requirements for the app.

  1. Allow pinch/zoom of a map with definable annotations
  2. Map must be available offline
  3. Map should be restricted to a certain geo area
  4. App does not have to pass Apple inspection. This is an enterprise app that will serve as a kiosk.

So, is there any framework or method of caching map tiles that anyone can suggest?

Thanks go out in advance.

Upvotes: 13

Views: 16526

Answers (5)

Vova Kalinichenko
Vova Kalinichenko

Reputation: 149

I used default map from MapKit and a subclass of MKTileOverlay to be able to save downloaded tiles and return already cached tiles without downloading them.

1) Change source for your default map from MapKit and use a subclass of MKTileOverlay (Used "open street map" here)

- (void)viewDidLoad{
    [super viewDidLoad];
    static NSString * const template = @"http://tile.openstreetmap.org/{z}/{x}/{y}.png";

    VHTileOverlay *overlay = [[VHTileOverlay alloc] initWithURLTemplate:template];
    overlay.canReplaceMapContent = YES;
    [self.mapView addOverlay:overlay level:MKOverlayLevelAboveLabels];
}

2) subclass from MKTileOverlay

@interface VHTileOverlay() // MKTileOverlay subclass
@property (nonatomic, strong) NSOperationQueue *operationQueue;
@end

@implementation VHTileOverlay

-(instancetype)initWithURLTemplate:(NSString *)URLTemplate{

    self = [super initWithURLTemplate:URLTemplate];
    if(self){
        self.directoryPath = cachePath;
        self.operationQueue = [NSOperationQueue new];
    }
    return self;
}


-(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;
    }

    NSString *pathToFilfe = [[self URLForTilePath:path] absoluteString];
    pathToFilfe = [pathToFilfe stringByReplacingOccurrencesOfString:@"/" withString:@"|"];
    // @"/" - those are not approriate for file's url...

    NSData *cachedData = [self loadFileWithName:pathToFilfe]; 
    if (cachedData) {
        result(cachedData, nil);
    } else {
        NSURLRequest *request = [NSURLRequest requestWithURL:[self URLForTilePath:path]];
        __block VHTileOverlay *weakSelf = self;
        [NSURLConnection sendAsynchronousRequest:request
                                           queue:self.operationQueue
                               completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
                                   NSLog(@"%@",[weakSelf URLForTilePath:path]);

                                   if(data){
                                       [self saveFileWithName:[[weakSelf URLForTilePath:path] absoluteString] imageData:data];
                                   }
                                   result(data, connectionError);
        }];
    }
}

-(NSString *)pathToImageWithName:(NSString *)fileName
{
    NSString *imageFilePath = [[OfflineMapCache sharedObject].cachePath stringByAppendingPathComponent:fileName];
    return imageFilePath;
}

- (NSData *)loadFileWithName:(NSString *)fileName
{
    NSString *imagePath = [self pathToImageWithName:fileName];
    NSData *data = [[NSData alloc] initWithContentsOfFile:imagePath];
    return data;
}

- (void)saveFileWithName:(NSString *)fileName imageData:(NSData *)imageData
{
//    fileName = [fileName stringByReplacingOccurrencesOfString:@"/" withString:@"|"];
//    NSString *imagePath = [self pathToImageWithName:fileName];
//    [imageData writeToFile:imagePath atomically:YES];
}

Uncomment "saveFileWithName" and run it on simulator. You can also add NSLog(fileName) to know where to get all tiles you need. (Simulator cache is in Users/YOU/Library/Developer/CoreSimulator/Devices/... And Library is a hidden directory)

After you cached all you need just put in your app's bundle (just like an any other image, if you want from box cached map). And tell your

- (void)loadTileAtPath:(MKTileOverlayPath)path
                result:(void (^)(NSData *data, NSError *error))result

to get tiles from bundle.

So now I can install my app, turn wi-fi off and I'll get those maps anyway.

Upvotes: 10

Ando
Ando

Reputation: 11409

See the skobbler/Telenav SDK - it's based on OpenStreetMap and is an (almost) full stack replacement to mapkit (map rendering, routing & turn by turn navigation) with integrated support for offline maps

Upvotes: 1

Niranjala S
Niranjala S

Reputation: 89

Unfortunately MapKit Framework doesn't support Offline Map Access. You Should prefer MapBox iOS SDK (MapBox iOS SDK is a toolset for building maps applications which support offline caching policy, zooming limits, retina display behavior, starting coordinate, and map view dragging deceleration etc....

Find the example link

Happy Coding

Upvotes: 1

ahwulf
ahwulf

Reputation: 2584

Try using http://mapbox.com/ which has both an iOS SDK and an app for constructing offline maps.

Upvotes: 3

Caleb
Caleb

Reputation: 124997

Check section 10.3 of the Google Maps Terms of Service and you'll find that you're not allowed to store any Google Maps content. That means that you'll need to provide not only your own map, but also replace the handy MapKit functionality. You really can't use MapKit at all for offline applications, as far as I can tell.

Upvotes: 1

Related Questions