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 6, 2016
1 parent 0875a06 commit 1304a31
Show file tree
Hide file tree
Showing 13 changed files with 515 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
27 changes: 27 additions & 0 deletions platform/darwin/include/MGLDownloadController.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#import <Foundation/Foundation.h>

#import "MGLTypes.h"

NS_ASSUME_NONNULL_BEGIN

@class MGLDownloadable;
@protocol MGLDownloadRegion;

typedef void (^MGLDownloadableRegistrationCompletionHandler)(MGLDownloadable *downloadable, 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)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
47 changes: 47 additions & 0 deletions platform/darwin/include/MGLDownloadable.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#import <Foundation/Foundation.h>

#import "MGLDownloadRegion.h"

NS_ASSUME_NONNULL_BEGIN

@protocol MGLDownloadableDelegate;

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

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;

@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
20 changes: 20 additions & 0 deletions platform/darwin/include/MGLTilePyramidDownloadRegion.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#import <Foundation/Foundation.h>

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

NS_ASSUME_NONNULL_BEGIN

@interface MGLTilePyramidDownloadRegion : NSObject <MGLDownloadRegion>

@property (nonatomic, readonly) NSURL *styleURL;
@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
129 changes: 129 additions & 0 deletions platform/darwin/src/MGLDownloadController.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#import "MGLDownloadController.h"

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

#include <mbgl/storage/default_file_source.hpp>
#include <mbgl/util/string.hpp>

@interface MGLDownloadController ()

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

@end

@implementation MGLDownloadController {
mbgl::DefaultFileSource *_mbglFileSource;
}

+ (instancetype)sharedController {
static dispatch_once_t onceToken;
static MGLDownloadController *sharedController;
dispatch_once(&onceToken, ^{
sharedController = [[self alloc] initWithName:@"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 mbgl::Map’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]]) {
_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::OfflineRegionDefinition regionDefinition = [(id <MGLDownloadRegion_Private>)downloadRegion offlineRegionDefinition];
mbgl::OfflineRegionMetadata metadata;
metadata.reserve(context.length);
[context getBytes:&metadata length:metadata.capacity()];
_mbglFileSource->createOfflineRegion(regionDefinition, metadata, [&](std::exception_ptr exception, mbgl::optional<mbgl::OfflineRegion> region) {
dispatch_async(dispatch_get_main_queue(), [&](void) {
NSError *error;
if (exception) {
error = [NSError errorWithDomain:MGLErrorDomain code:-1 userInfo:@{
NSLocalizedDescriptionKey: @(mbgl::util::toString(exception).c_str()),
}];
}
if (completion) {
MGLDownloadable *downloadable = [[MGLDownloadable alloc] initWithMBGLRegion:new mbgl::OfflineRegion(std::move(*region))];
completion(downloadable, error);
}
});
});
}

- (void)resumeDownloadable:(MGLDownloadable *)downloadable {
_mbglFileSource->setOfflineRegionDownloadState(*downloadable.mbglOfflineRegion, mbgl::OfflineRegionDownloadState::Active);
}

- (void)suspendDownloadable:(MGLDownloadable *)downloadable {
_mbglFileSource->setOfflineRegionDownloadState(*downloadable.mbglOfflineRegion, mbgl::OfflineRegionDownloadState::Inactive);
}

- (void)requestDownloadablesWithCompletionHandler:(MGLDownloadablesRequestCompletionHandler)completion {
_mbglFileSource->listOfflineRegions([&](std::exception_ptr exception, mbgl::optional<std::vector<mbgl::OfflineRegion>> regions) {
dispatch_async(dispatch_get_main_queue(), [&](void) {
NSError *error;
if (exception) {
error = [NSError errorWithDomain:MGLErrorDomain code:-1 userInfo:@{
NSLocalizedDescriptionKey: @(mbgl::util::toString(exception).c_str()),
}];
}
if (completion) {
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))];
mbgl::OfflineRegionObserver *observer = downloadable.mbglOfflineRegionObserver;
_mbglFileSource->setOfflineRegionObserver(*downloadable.mbglOfflineRegion, std::make_unique<mbgl::OfflineRegionObserver>(*observer));
[downloadables addObject:downloadable];
}
}
completion(downloadables, error);
}
});
});
}

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

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

#import "MGLDownloadable.h"

@interface MGLDownloadController (Private)

- (void)resumeDownloadable:(MGLDownloadable *)downloadable;
- (void)suspendDownloadable:(MGLDownloadable *)downloadable;

@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 1304a31

Please sign in to comment.