Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Commit

Permalink
[ios] Implemented offline API in iOS SDK
Browse files Browse the repository at this point in the history
Fixes #3892.
  • Loading branch information
1ec5 committed Mar 11, 2016
1 parent a657308 commit 77657b3
Show file tree
Hide file tree
Showing 14 changed files with 545 additions and 1 deletion.
7 changes: 6 additions & 1 deletion gyp/platform-ios.gypi
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@
'../platform/darwin/src/MGLPolyline.mm',
'../platform/darwin/src/MGLPolygon.mm',
'../platform/darwin/src/MGLMapCamera.mm',
'../platform/darwin/src/MGLDownloadable.mm',
'../platform/darwin/src/MGLDownloadable_Private.h',
'../platform/darwin/src/MGLDownloadController.mm',
'../platform/darwin/src/MGLDownloadController_Private.h',
'../platform/darwin/src/MGLDownloadRegion_Private.h',
'../platform/darwin/src/MGLTilePyramidDownloadRegion.mm',
'../platform/ios/src/MGLMapboxEvents.h',
'../platform/ios/src/MGLMapboxEvents.m',
'../platform/ios/src/MGLAPIClient.h',
Expand All @@ -59,7 +65,6 @@
'../platform/ios/src/MGLUserLocationAnnotationView.m',
'../platform/ios/src/MGLAnnotationImage_Private.h',
'../platform/ios/src/MGLAnnotationImage.m',
'../platform/ios/include/MGLCalloutView.h',
'../platform/ios/src/MGLCompactCalloutView.h',
'../platform/ios/src/MGLCompactCalloutView.m',
'../platform/ios/src/NSBundle+MGLAdditions.h',
Expand Down
30 changes: 30 additions & 0 deletions platform/darwin/include/MGLDownloadController.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#import <Foundation/Foundation.h>

#import "MGLTypes.h"

NS_ASSUME_NONNULL_BEGIN

@class MGLDownloadable;
@protocol MGLDownloadRegion;

typedef void (^MGLDownloadableRegistrationCompletionHandler)(MGLDownloadable *downloadable, NSError *error);
typedef void (^MGLDownloadableRemovalCompletionHandler)(NSError *error);
typedef void (^MGLDownloadablesRequestCompletionHandler)(NS_ARRAY_OF(MGLDownloadable *) *downloadables, NSError *error);

@interface MGLDownloadController : NSObject

+ (instancetype)sharedController;

- (instancetype)init NS_UNAVAILABLE;

- (void)addDownloadableForRegion:(id <MGLDownloadRegion>)downloadRegion withContext:(NSData *)context completionHandler:(MGLDownloadableRegistrationCompletionHandler)completion;

- (void)removeDownloadable:(MGLDownloadable *)downloadable withCompletionHandler:(MGLDownloadableRemovalCompletionHandler)completion;

- (void)requestDownloadablesWithCompletionHandler:(MGLDownloadablesRequestCompletionHandler)completion;

- (void)setMaximumAllowedMapboxTiles:(uint64_t)maximumCount;

@end

NS_ASSUME_NONNULL_END
13 changes: 13 additions & 0 deletions platform/darwin/include/MGLDownloadRegion.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#import <Foundation/Foundation.h>

#import "MGLTypes.h"

NS_ASSUME_NONNULL_BEGIN

@protocol MGLDownloadRegion <NSObject>

@property (nonatomic, readonly) NSURL *styleURL;

@end

NS_ASSUME_NONNULL_END
49 changes: 49 additions & 0 deletions platform/darwin/include/MGLDownloadable.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#import <Foundation/Foundation.h>

#import "MGLDownloadRegion.h"

NS_ASSUME_NONNULL_BEGIN

@protocol MGLDownloadableDelegate;

typedef NS_ENUM (NSInteger, MGLDownloadableState) {
MGLDownloadableStateInactive = 0,
MGLDownloadableStateActive = 1,
MGLDownloadableStateComplete = 2,
};

typedef struct MGLDownloadableProgress {
uint64_t countOfResourcesCompleted;
uint64_t countOfBytesCompleted;
uint64_t countOfResourcesExpected;
uint64_t maximumResourcesExpected;
} MGLDownloadableProgress;

@interface MGLDownloadable : NSObject

@property (nonatomic, readonly) id <MGLDownloadRegion> region;
@property (nonatomic, readonly) NSData *context;
@property (nonatomic, readonly) MGLDownloadableState state;
@property (nonatomic, readonly) MGLDownloadableProgress progress;
@property (nonatomic, weak, nullable) id <MGLDownloadableDelegate> delegate;

- (instancetype)init NS_UNAVAILABLE;

- (void)resume;
- (void)suspend;

- (void)requestProgress;

@end

@protocol MGLDownloadableDelegate <NSObject>

@optional

- (void)downloadable:(MGLDownloadable *)downloadable progressDidChange:(MGLDownloadableProgress)progress;
- (void)downloadable:(MGLDownloadable *)downloadable didReceiveError:(NSError *)error;
- (void)downloadable:(MGLDownloadable *)downloadable didReceiveMaximumAllowedMapboxTiles:(uint64_t)maximumCount;

@end

NS_ASSUME_NONNULL_END
19 changes: 19 additions & 0 deletions platform/darwin/include/MGLTilePyramidDownloadRegion.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#import <Foundation/Foundation.h>

#import "MGLDownloadRegion.h"
#import "MGLGeometry.h"

NS_ASSUME_NONNULL_BEGIN

@interface MGLTilePyramidDownloadRegion : NSObject <MGLDownloadRegion>

@property (nonatomic, readonly) MGLCoordinateBounds bounds;
@property (nonatomic, readonly) double minimumZoomLevel;
@property (nonatomic, readonly) double maximumZoomLevel;

- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithStyleURL:(nullable NSURL *)styleURL bounds:(MGLCoordinateBounds)bounds fromZoomLevel:(double)minimumZoomLevel toZoomLevel:(double)maximumZoomLevel NS_DESIGNATED_INITIALIZER;

@end

NS_ASSUME_NONNULL_END
7 changes: 7 additions & 0 deletions platform/darwin/include/MGLTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ NS_ASSUME_NONNULL_BEGIN
/** Indicates an error occurred in the Mapbox SDK. */
extern NSString * const MGLErrorDomain;

typedef NS_ENUM(NSInteger, MGLErrorCode) {
MGLErrorCodeUnknown = -1,
MGLErrorCodeNotFound = 1,
MGLErrorCodeBadServerResponse = 2,
MGLErrorCodeConnectionFailed = 3,
};

/** The mode used to track the user location on the map. */
typedef NS_ENUM(NSUInteger, MGLUserTrackingMode) {
/** The map does not follow the user location. */
Expand Down
134 changes: 134 additions & 0 deletions platform/darwin/src/MGLDownloadController.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
#import "MGLDownloadController_Private.h"

#import "MGLAccountManager_Private.h"
#import "MGLGeometry_Private.h"
#import "MGLDownloadable_Private.h"
#import "MGLDownloadRegion_Private.h"
#import "MGLTilePyramidDownloadRegion.h"

#include <mbgl/util/string.hpp>

@interface MGLDownloadController ()

@property (nonatomic) mbgl::DefaultFileSource *mbglFileSource;

- (instancetype)initWithFileName:(NSString *)fileName NS_DESIGNATED_INITIALIZER;

@end

@implementation MGLDownloadController

+ (instancetype)sharedController {
static dispatch_once_t onceToken;
static MGLDownloadController *sharedController;
dispatch_once(&onceToken, ^{
sharedController = [[self alloc] initWithFileName:@"offline.db"];
});
return sharedController;
}

- (instancetype)initWithFileName:(NSString *)fileName {
if (self = [super init]) {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *fileCachePath = [paths.firstObject stringByAppendingPathComponent:fileName];
_mbglFileSource = new mbgl::DefaultFileSource(fileCachePath.UTF8String, [NSBundle mainBundle].resourceURL.path.UTF8String);

// Observe for changes to the global access token (and find out the current one).
[[MGLAccountManager sharedManager] addObserver:self
forKeyPath:@"accessToken"
options:(NSKeyValueObservingOptionInitial |
NSKeyValueObservingOptionNew)
context:NULL];
}
return self;
}

- (void)dealloc {
[[MGLAccountManager sharedManager] removeObserver:self forKeyPath:@"accessToken"];

delete _mbglFileSource;
_mbglFileSource = nullptr;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NS_DICTIONARY_OF(NSString *, id) *)change context:(__unused void *)context {
// Synchronize the file source’s access token with the global one in MGLAccountManager.
if ([keyPath isEqualToString:@"accessToken"] && object == [MGLAccountManager sharedManager]) {
NSString *accessToken = change[NSKeyValueChangeNewKey];
if (![accessToken isKindOfClass:[NSNull class]]) {
self.mbglFileSource->setAccessToken(accessToken.UTF8String);
}
}
}

- (void)addDownloadableForRegion:(id <MGLDownloadRegion>)downloadRegion withContext:(NSData *)context completionHandler:(MGLDownloadableRegistrationCompletionHandler)completion {
if (![downloadRegion conformsToProtocol:@protocol(MGLDownloadRegion_Private)]) {
[NSException raise:@"Unsupported region type" format:
@"Regions of type %@ are unsupported.", NSStringFromClass([downloadRegion class])];
return;
}

const mbgl::OfflineTilePyramidRegionDefinition regionDefinition = [(id <MGLDownloadRegion_Private>)downloadRegion offlineRegionDefinition];
mbgl::OfflineRegionMetadata metadata(context.length);
[context getBytes:&metadata[0] length:metadata.size()];
self.mbglFileSource->createOfflineRegion(regionDefinition, metadata, [&, completion](std::exception_ptr exception, mbgl::optional<mbgl::OfflineRegion> region) {
NSError *error;
if (exception) {
NSString *errorDescription = @(mbgl::util::toString(exception).c_str());
error = [NSError errorWithDomain:MGLErrorDomain code:-1 userInfo:errorDescription ? @{
NSLocalizedDescriptionKey: errorDescription,
} : nil];
}
if (completion) {
MGLDownloadable *downloadable = [[MGLDownloadable alloc] initWithMBGLRegion:new mbgl::OfflineRegion(std::move(*region))];
dispatch_async(dispatch_get_main_queue(), [&, completion, error, downloadable](void) {
completion(downloadable, error);
});
}
});
}

- (void)removeDownloadable:(MGLDownloadable *)downloadable withCompletionHandler:(MGLDownloadableRemovalCompletionHandler)completion {
self.mbglFileSource->deleteOfflineRegion(std::move(*downloadable.mbglOfflineRegion), [&, completion](std::exception_ptr exception) {
NSError *error;
if (exception) {
error = [NSError errorWithDomain:MGLErrorDomain code:-1 userInfo:@{
NSLocalizedDescriptionKey: @(mbgl::util::toString(exception).c_str()),
}];
}
if (completion) {
dispatch_async(dispatch_get_main_queue(), [&, completion, error](void) {
completion(error);
});
}
});
}

- (void)requestDownloadablesWithCompletionHandler:(MGLDownloadablesRequestCompletionHandler)completion {
self.mbglFileSource->listOfflineRegions([&, completion](std::exception_ptr exception, mbgl::optional<std::vector<mbgl::OfflineRegion>> regions) {
NSError *error;
if (exception) {
error = [NSError errorWithDomain:MGLErrorDomain code:-1 userInfo:@{
NSLocalizedDescriptionKey: @(mbgl::util::toString(exception).c_str()),
}];
}
NSMutableArray *downloadables;
if (regions) {
downloadables = [NSMutableArray arrayWithCapacity:regions->size()];
for (mbgl::OfflineRegion &region : *regions) {
MGLDownloadable *downloadable = [[MGLDownloadable alloc] initWithMBGLRegion:new mbgl::OfflineRegion(std::move(region))];
[downloadables addObject:downloadable];
}
}
if (completion) {
dispatch_async(dispatch_get_main_queue(), [&, completion, error, downloadables](void) {
completion(downloadables, error);
});
}
});
}

- (void)setMaximumAllowedMapboxTiles:(uint64_t)maximumCount {
_mbglFileSource->setOfflineMapboxTileCountLimit(maximumCount);
}

@end
15 changes: 15 additions & 0 deletions platform/darwin/src/MGLDownloadController_Private.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#import "MGLDownloadController.h"

#import "MGLDownloadable.h"

#include <mbgl/storage/default_file_source.hpp>

NS_ASSUME_NONNULL_BEGIN

@interface MGLDownloadController (Private)

@property (nonatomic) mbgl::DefaultFileSource *mbglFileSource;

@end

NS_ASSUME_NONNULL_END
17 changes: 17 additions & 0 deletions platform/darwin/src/MGLDownloadRegion_Private.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#import <Foundation/Foundation.h>

#import "MGLDownloadRegion.h"

#include <mbgl/storage/offline.hpp>

NS_ASSUME_NONNULL_BEGIN

@protocol MGLDownloadRegion_Private <MGLDownloadRegion>

- (instancetype)initWithOfflineRegionDefinition:(const mbgl::OfflineRegionDefinition &)definition;

- (const mbgl::OfflineRegionDefinition)offlineRegionDefinition;

@end

NS_ASSUME_NONNULL_END
Loading

0 comments on commit 77657b3

Please sign in to comment.