From 9c6e059d3111d4e299edba9150843bf9c0dfbbc1 Mon Sep 17 00:00:00 2001 From: Bobby Sudekum Date: Mon, 18 Apr 2016 15:42:16 -0700 Subject: [PATCH] Add ios offline support (#312) * Add ios offline * updates * change getpacks name, add metadat field to pack * add get bounds method --- index.ios.js | 34 +++++- ios/API.md | 10 +- ios/RCTMapboxGL.xcodeproj/project.pbxproj | 4 - ios/RCTMapboxGL/RCTMapboxGL.h | 2 + ios/RCTMapboxGL/RCTMapboxGL.m | 81 +++++++++++++ ios/RCTMapboxGL/RCTMapboxGLManager.m | 137 +++++++++++++++++++++- ios/example.js | 46 ++++++-- package.json | 2 +- 8 files changed, 293 insertions(+), 23 deletions(-) diff --git a/index.ios.js b/index.ios.js index 1987309..05dbfaa 100644 --- a/index.ios.js +++ b/index.ios.js @@ -4,6 +4,15 @@ var React = require('react-native'); var { NativeModules, requireNativeComponent } = React; var MapMixins = { + addPackForRegion(mapRef, options) { + NativeModules.MapboxGLManager.addPackForRegion(React.findNodeHandle(this.refs[mapRef]), options); + }, + getPacks(mapRef, callback) { + NativeModules.MapboxGLManager.getPacks(React.findNodeHandle(this.refs[mapRef]), callback); + }, + removePack(mapRef, packName, callback) { + NativeModules.MapboxGLManager.removePack(React.findNodeHandle(this.refs[mapRef]), packName, callback); + }, setDirectionAnimated(mapRef, heading) { NativeModules.MapboxGLManager.setDirectionAnimated(React.findNodeHandle(this.refs[mapRef]), heading); }, @@ -46,9 +55,13 @@ var MapMixins = { getDirection(mapRef, callback) { NativeModules.MapboxGLManager.getDirection(React.findNodeHandle(this.refs[mapRef]), callback); }, + getBounds(mapRef, callback) { + NativeModules.MapboxGLManager.getBounds(React.findNodeHandle(this.refs[mapRef]), callback); + }, mapStyles: NativeModules.MapboxGLManager.mapStyles, userTrackingMode: NativeModules.MapboxGLManager.userTrackingMode, - userLocationVerticalAlignment: NativeModules.MapboxGLManager.userLocationVerticalAlignment + userLocationVerticalAlignment: NativeModules.MapboxGLManager.userLocationVerticalAlignment, + unknownResourceCount: NativeModules.MapboxGLManager.unknownResourceCount }; var MapView = React.createClass({ @@ -85,6 +98,15 @@ var MapView = React.createClass({ _onLocateUserFailed(event: Event) { if (this.props.onLocateUserFailed) this.props.onLocateUserFailed(event.nativeEvent.src); }, + _onOfflineProgressDidChange(event: Event) { + if (this.props.onOfflineProgressDidChange) this.props.onOfflineProgressDidChange(event.nativeEvent.src); + }, + _onOfflineMaxAllowedMapboxTiles(event: Event) { + if (this.props.onOfflineMaxAllowedMapboxTiles) this.props.onOfflineMaxAllowedMapboxTiles(event.nativeEvent.src); + }, + _onOfflineDidRecieveError(event: Event) { + if (this.props.onOfflineDidRecieveError) this.props.onOfflineDidRecieveError(event.nativeEvent.src); + }, propTypes: { showsUserLocation: React.PropTypes.bool, rotateEnabled: React.PropTypes.bool, @@ -138,7 +160,10 @@ var MapView = React.createClass({ onLongPress: React.PropTypes.func, onTap: React.PropTypes.func, contentInset: React.PropTypes.array, - userLocationVerticalAlignment: React.PropTypes.number + userLocationVerticalAlignment: React.PropTypes.number, + onOfflineProgressDidChange: React.PropTypes.func, + onOfflineMaxAllowedMapboxTiles: React.PropTypes.func, + onOfflineDidRecieveError: React.PropTypes.func }, getDefaultProps() { return { @@ -172,7 +197,10 @@ var MapView = React.createClass({ onTap={this._onTap} onFinishLoadingMap={this._onFinishLoadingMap} onStartLoadingMap={this._onStartLoadingMap} - onLocateUserFailed={this._onLocateUserFailed} /> + onLocateUserFailed={this._onLocateUserFailed} + onOfflineProgressDidChange={this._onOfflineProgressDidChange} + onOfflineMaxAllowedMapboxTiles={this._onOfflineMaxAllowedMapboxTiles} + onOfflineDidRecieveError={this._onOfflineDidRecieveError} /> ); } }); diff --git a/ios/API.md b/ios/API.md index 55c6100..2e764dd 100644 --- a/ios/API.md +++ b/ios/API.md @@ -39,9 +39,10 @@ | `onLocateUserFailed` | `{message: message}` | Fired when there is an error getting the users location. Do not rely on the string that is returned for determining what kind of error it is. | | `getCenterCoordinateZoomLevel` | `mapViewRef`, `callback` | Gets the current center location and zoom level. Returns a single callback object. | | `getDirection` | `mapViewRef`, `callback` | Gets the current direction. Returns a single callback object. | - - - +| `getBounds` | `mapViewRef`, `callback` | Gets the bounds of the current view. Returns array [latitudeSW, longitudeSW, latitudeNE, longitudeNE]. | +| `onOfflineProgressDidChange` | `{countOfResourcesCompleted: 7, countOfResourcesExpected: 1284, name: "test", countOfBytesCompleted: 306543, maximumResourcesExpected: 1284}` | Event fired when the progress of an offline pack changes while downloading. | +| `onOfflineMaxAllowedMapboxTiles` | `{maximumCount: number}` | Event fired when the maximum number of tiles has been hit. | +| `onOfflineDidRecieveError` | `{error: error}` | Event fired when there is an error while downloading a pack. | ## Methods for Modifying the Map State @@ -61,6 +62,9 @@ These methods require you to use `MapboxGLMap.Mixin` to access the methods. Each | `removeAllAnnotations` | `mapViewRef`| Removes all annotations from the map. | `setVisibleCoordinateBoundsAnimated` | `mapViewRef`, `latitude1`, `longitude1`, `latitude2`, `longitude2`, `padding top`, `padding right`, `padding bottom`, `padding left` | Changes the viewport to fit the given coordinate bounds and some additional padding on each side. | `setUserTrackingMode` | `mapViewRef`, `userTrackingMode` | Modifies the tracking mode. Valid args: `this.userTrackingMode.none`, `this.userTrackingMode.follow`, `this.userTrackingMode.followWithCourse`, `this.userTrackingMode.followWithHeading` +| `addPackForRegion` | `mapRef` `{name, type, bounds, minZoomLevel, maxZoomLevel, style}` | Adds an offline region for a given bounding box. `name` is a string to represent an offline pack. `type` must be of type `bbox`. `bounds` is an array. `minZoomLevel` is an number representing the minimum zoom level of the offline pack. `maxZoomLevel` is an number representing the maximum zoom level of the offline pack. `style` is a style url to download. `metadata` is an object you can use metadata about the pack. +| `getPacks` | `mapRef` `callback` | Returns a callback with an array of all offline packs on device. If the downloaded pack was not downloaded during the current session, the size will be 0. +| `removePack` | `mapRef` `name-of-pack` `callback` | Removes a pack from the device. The name corresponds to the `name` of the pack used when calling `addPackForRegion` ## Styles diff --git a/ios/RCTMapboxGL.xcodeproj/project.pbxproj b/ios/RCTMapboxGL.xcodeproj/project.pbxproj index 480068c..3eea7e8 100644 --- a/ios/RCTMapboxGL.xcodeproj/project.pbxproj +++ b/ios/RCTMapboxGL.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - C5AF3B8C1C5A8C3D0003BF64 /* Mapbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C5AF3B8B1C5A8C3D0003BF64 /* Mapbox.framework */; }; C5DBB3441AF2EF2B00E611A9 /* RCTMapboxGL.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = C5DBB3431AF2EF2B00E611A9 /* RCTMapboxGL.h */; }; C5DBB3461AF2EF2B00E611A9 /* RCTMapboxGL.m in Sources */ = {isa = PBXBuildFile; fileRef = C5DBB3451AF2EF2B00E611A9 /* RCTMapboxGL.m */; }; C5DBB34C1AF2EF2B00E611A9 /* libRCTMapboxGL.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C5DBB3401AF2EF2B00E611A9 /* libRCTMapboxGL.a */; }; @@ -38,7 +37,6 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - C5AF3B8B1C5A8C3D0003BF64 /* Mapbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Mapbox.framework; sourceTree = ""; }; C5DBB3401AF2EF2B00E611A9 /* libRCTMapboxGL.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTMapboxGL.a; sourceTree = BUILT_PRODUCTS_DIR; }; C5DBB3431AF2EF2B00E611A9 /* RCTMapboxGL.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTMapboxGL.h; sourceTree = ""; }; C5DBB3451AF2EF2B00E611A9 /* RCTMapboxGL.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RCTMapboxGL.m; sourceTree = ""; }; @@ -53,7 +51,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - C5AF3B8C1C5A8C3D0003BF64 /* Mapbox.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -71,7 +68,6 @@ C5DBB3371AF2EF2B00E611A9 = { isa = PBXGroup; children = ( - C5AF3B8B1C5A8C3D0003BF64 /* Mapbox.framework */, C5DBB3421AF2EF2B00E611A9 /* RCTMapboxGL */, C5DBB34F1AF2EF2B00E611A9 /* RCTMapboxGLTests */, C5DBB3411AF2EF2B00E611A9 /* Products */, diff --git a/ios/RCTMapboxGL/RCTMapboxGL.h b/ios/RCTMapboxGL/RCTMapboxGL.h index e14ac24..1a5625c 100644 --- a/ios/RCTMapboxGL/RCTMapboxGL.h +++ b/ios/RCTMapboxGL/RCTMapboxGL.h @@ -37,12 +37,14 @@ - (void)addAnnotation:(NSObject *)annotation; - (void)removeAnnotation:(NSString*)selectedIdentifier; - (void)removeAllAnnotations; +- (MGLCoordinateBounds)visibleCoordinateBounds; - (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(UIEdgeInsets)padding animated:(BOOL)animated; - (void)setAttributionButtonVisibility:(BOOL)isVisible; - (void)setLogoVisibility:(BOOL)isVisible; - (void)setCompassVisibility:(BOOL)isVisible; - (double)zoomLevel; - (double)direction; +- (void) createOfflinePack:(MGLCoordinateBounds)bounds styleURL:(NSURL*)styleURL fromZoomLevel:(double)fromZoomLevel toZoomLevel:(double)toZoomLevel name:(NSString*)name type:(NSString*)type metadata:(NSDictionary*)metadata; - (CLLocationCoordinate2D)centerCoordinate; @property (nonatomic) MGLAnnotationVerticalAlignment userLocationVerticalAlignment; @property (nonatomic) UIEdgeInsets contentInset; diff --git a/ios/RCTMapboxGL/RCTMapboxGL.m b/ios/RCTMapboxGL/RCTMapboxGL.m index b26cedb..360d3e1 100644 --- a/ios/RCTMapboxGL/RCTMapboxGL.m +++ b/ios/RCTMapboxGL/RCTMapboxGL.m @@ -12,6 +12,10 @@ #import "UIView+React.h" #import "RCTLog.h" +@interface RCTMapboxGL () +@property (nonatomic) MGLOfflinePack *pack; +@end + @implementation RCTMapboxGL { /* Required to publish events */ RCTEventDispatcher *_eventDispatcher; @@ -91,6 +95,7 @@ RCT_EXPORT_MODULE(); } } + - (void)createMap { [MGLAccountManager setAccessToken:_accessToken]; @@ -104,12 +109,35 @@ RCT_EXPORT_MODULE(); UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTap:)]; singleTap.delegate = self; [_map addGestureRecognizer:singleTap]; + + // Setup offline pack notification handlers. + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(offlinePackProgressDidChange:) name:MGLOfflinePackProgressChangedNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(offlinePackDidReceiveError:) name:MGLOfflinePackErrorNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(offlinePackDidReceiveMaximumAllowedMapboxTiles:) name:MGLOfflinePackMaximumMapboxTilesReachedNotification object:nil]; [self updateMap]; [self addSubview:_map]; [self layoutSubviews]; } +-(void)createOfflinePack:(MGLCoordinateBounds)bounds styleURL:(NSURL*)styleURL fromZoomLevel:(double)fromZoomLevel toZoomLevel:(double)toZoomLevel name:(NSString*)name type:(NSString*)type metadata:(NSDictionary *)metadata +{ + + id region = [[MGLTilePyramidOfflineRegion alloc] initWithStyleURL:styleURL bounds:bounds fromZoomLevel:fromZoomLevel toZoomLevel:toZoomLevel]; + + NSMutableDictionary *userInfo = [metadata mutableCopy]; + userInfo[@"name"] = name; + NSData *context = [NSKeyedArchiver archivedDataWithRootObject:userInfo]; + + [[MGLOfflineStorage sharedOfflineStorage] addPackForRegion:region withContext:context completionHandler:^(MGLOfflinePack *pack, NSError *error) { + if (error != nil) { + RCTLogError(@"Error: %@", error.localizedFailureReason); + } else { + [pack resume]; + } + }]; +} + - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { return YES; @@ -256,6 +284,11 @@ RCT_EXPORT_MODULE(); [self updateMap]; } +- (MGLCoordinateBounds) visibleCoordinateBounds +{ + return [_map visibleCoordinateBounds]; +} + - (void)setUserTrackingMode:(int)userTrackingMode { if (userTrackingMode > 3 || userTrackingMode < 0) { @@ -502,6 +535,54 @@ RCT_EXPORT_MODULE(); [_eventDispatcher sendInputEventWithName:@"onStartLoadingMap" body:event]; } +- (void)offlinePackProgressDidChange:(NSNotification *)notification { + + MGLOfflinePack *pack = notification.object; + NSDictionary *userInfo = [NSKeyedUnarchiver unarchiveObjectWithData:pack.context]; + MGLOfflinePackProgress progress = pack.progress; + + NSDictionary *event = @{ @"target": self.reactTag, + @"src": @{ + @"name": userInfo[@"name"], + @"countOfResourcesCompleted": @(progress.countOfResourcesCompleted), + @"countOfResourcesExpected": @(progress.countOfResourcesExpected), + @"countOfBytesCompleted": @(progress.countOfBytesCompleted), + @"maximumResourcesExpected": @(progress.maximumResourcesExpected) + } + }; + + [_eventDispatcher sendInputEventWithName:@"onOfflineProgressDidChange" body:event]; +} + +- (void)offlinePackDidReceiveMaximumAllowedMapboxTiles:(NSNotification *)notification { + MGLOfflinePack *pack = notification.object; + NSDictionary *userInfo = [NSKeyedUnarchiver unarchiveObjectWithData:pack.context]; + uint64_t maximumCount = [notification.userInfo[MGLOfflinePackMaximumCountUserInfoKey] unsignedLongLongValue]; + + NSDictionary *event = @{ @"target": self.reactTag, + @"src": @{ + @"name": userInfo[@"name"], + @"maxTiles": @(maximumCount) + } + }; + [_eventDispatcher sendInputEventWithName:@"onOfflineMaxAllowedMapboxTiles" body:event]; +} + +- (void)offlinePackDidReceiveError:(NSNotification *)notification { + MGLOfflinePack *pack = notification.object; + NSDictionary *userInfo = [NSKeyedUnarchiver unarchiveObjectWithData:pack.context]; + NSError *error = notification.userInfo[MGLOfflinePackErrorUserInfoKey]; + + NSDictionary *event = @{ @"target": self.reactTag, + @"src": @{ + @"name": userInfo[@"name"], + @"error": [error localizedDescription] + } + }; + [_eventDispatcher sendInputEventWithName:@"onOfflineDidRecieveError" body:event]; +} + + - (void)mapView:(MGLMapView *)mapView didFailToLocateUserWithError:(NSError *)error { NSDictionary *event = @{ @"target": mapView.reactTag, diff --git a/ios/RCTMapboxGL/RCTMapboxGLManager.m b/ios/RCTMapboxGL/RCTMapboxGLManager.m index 0a8e197..10070c8 100644 --- a/ios/RCTMapboxGL/RCTMapboxGLManager.m +++ b/ios/RCTMapboxGL/RCTMapboxGLManager.m @@ -18,6 +18,7 @@ @implementation RCTMapboxGLManager + RCT_EXPORT_MODULE(); @synthesize bridge = _bridge; @@ -43,7 +44,10 @@ RCT_EXPORT_MODULE(); @"onLongPress", @"onFinishLoadingMap", @"onStartLoadingMap", - @"onLocateUserFailed" + @"onLocateUserFailed", + @"onOfflineProgressDidChange", + @"onOfflineMaxAllowedMapboxTiles", + @"onOfflineDidRecieveError" ]; } @@ -68,7 +72,8 @@ RCT_EXPORT_MODULE(); @"top": @(MGLAnnotationVerticalAlignmentTop), @"center": @(MGLAnnotationVerticalAlignmentCenter), @"bottom": @(MGLAnnotationVerticalAlignmentBottom) - } + }, + @"unknownResourceCount": @(UINT64_MAX) }; }; @@ -87,7 +92,7 @@ RCT_EXPORT_VIEW_PROPERTY(zoomLevel, double); RCT_EXPORT_VIEW_PROPERTY(userLocationVerticalAlignment, int); RCT_EXPORT_METHOD(getCenterCoordinateZoomLevel:(nonnull NSNumber *)reactTag - findEvents:(RCTResponseSenderBlock)callback) + callback:(RCTResponseSenderBlock)callback) { [_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { RCTMapboxGL *mapView = viewRegistry[reactTag]; @@ -104,8 +109,25 @@ RCT_EXPORT_METHOD(getCenterCoordinateZoomLevel:(nonnull NSNumber *)reactTag }]; } +RCT_EXPORT_METHOD(getBounds:(nonnull NSNumber *)reactTag + callback:(RCTResponseSenderBlock)callback) +{ + [_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { + RCTMapboxGL *mapView = viewRegistry[reactTag]; + MGLCoordinateBounds bounds = [mapView visibleCoordinateBounds]; + NSMutableArray *callbackArray = [[NSMutableArray alloc] init]; + + [callbackArray addObject:@(bounds.sw.latitude)]; + [callbackArray addObject:@(bounds.sw.longitude)]; + [callbackArray addObject:@(bounds.ne.latitude)]; + [callbackArray addObject:@(bounds.ne.longitude)]; + + callback(@[callbackArray]); + }]; +} + RCT_EXPORT_METHOD(getDirection:(nonnull NSNumber *)reactTag - findEvents:(RCTResponseSenderBlock)callback) + callback:(RCTResponseSenderBlock)callback) { [_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { RCTMapboxGL *mapView = viewRegistry[reactTag]; @@ -165,6 +187,111 @@ RCT_CUSTOM_VIEW_PROPERTY(compassIsHidden, BOOL, RCTMapboxGL) [view setCompassVisibility:value ? true : false]; } +RCT_EXPORT_METHOD(addPackForRegion:(nonnull NSNumber *)reactTag + options:(NSDictionary*)options) +{ + + [_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { + RCTMapboxGL *mapView = viewRegistry[reactTag]; + if ([mapView isKindOfClass:[RCTMapboxGL class]]) { + + if ([options objectForKey:@"name"] == nil) { + return RCTLogError(@"Name is required."); + } + if ([options objectForKey:@"minZoomLevel"] == nil) { + return RCTLogError(@"minZoomLevel is required."); + } + if ([options objectForKey:@"maxZoomLevel"] == nil) { + return RCTLogError(@"maxZoomLevel is required."); + } + if ([options objectForKey:@"bounds"] == nil) { + return RCTLogError(@"bounds is required."); + } + if ([options objectForKey:@"styleURL"] == nil) { + return RCTLogError(@"styleURL is required."); + } + if ([options objectForKey:@"metadata"] == nil) { + return RCTLogError(@"metadata is required."); + } + if (!([[options objectForKey:@"type"] isEqualToString:@"bbox"])) { + return RCTLogError(@"Offline type %@ not supported. Only type `bbox` supported.", [options valueForKey:@"type"]); + } + + NSArray *b = [options valueForKey:@"bounds"]; + MGLCoordinateBounds bounds = MGLCoordinateBoundsMake(CLLocationCoordinate2DMake([b[0] floatValue], [b[1] floatValue]), CLLocationCoordinate2DMake([b[2] floatValue], [b[3] floatValue])); + [mapView createOfflinePack:bounds styleURL:[NSURL URLWithString:[options valueForKey:@"styleURL"]] fromZoomLevel:[[options valueForKey:@"minZoomLevel"] floatValue] toZoomLevel:[[options valueForKey:@"maxZoomLevel"] floatValue] name:[options valueForKey:@"name"] type:[options valueForKey:@"type"] metadata:[options valueForKey:@"metadata"]]; + } + }]; +} + +RCT_EXPORT_METHOD(getPacks:(nonnull NSNumber *)reactTag + callback:(RCTResponseSenderBlock)callback) +{ + [_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { + RCTMapboxGL *mapView = viewRegistry[reactTag]; + if ([mapView isKindOfClass:[RCTMapboxGL class]]) { + RCTMapboxGL *mapView = viewRegistry[reactTag]; + NSMutableArray* callbackArray = [NSMutableArray new]; + + MGLOfflinePack *packs = [MGLOfflineStorage sharedOfflineStorage].packs; + + for (MGLOfflinePack *pack in packs) { + NSMutableDictionary *packDict = [NSMutableDictionary new]; + NSMutableDictionary *userInfo = [[NSKeyedUnarchiver unarchiveObjectWithData:pack.context] mutableCopy]; + [packDict setObject:userInfo[@"name"] forKey:@"name"]; + [userInfo removeObjectForKey:@"name"]; + [packDict setObject:userInfo forKey:@"metadata"]; + [packDict setObject:@(pack.progress.countOfBytesCompleted) forKey:@"countOfBytesCompleted"]; + [packDict setObject:@(pack.progress.countOfResourcesCompleted) forKey:@"countOfResourcesCompleted"]; + [callbackArray addObject:packDict]; + } + + callback(@[[NSNull null], callbackArray]); + } + }]; +} + +RCT_EXPORT_METHOD(removePack:(nonnull NSNumber *)reactTag + packName:(NSString*)packName + callback:(RCTResponseSenderBlock)callback) +{ + [_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { + RCTMapboxGL *mapView = viewRegistry[reactTag]; + if ([mapView isKindOfClass:[RCTMapboxGL class]]) { + RCTMapboxGL *mapView = viewRegistry[reactTag]; + + MGLOfflinePack *packs = [MGLOfflineStorage sharedOfflineStorage].packs; + MGLOfflinePack *tempPack; + + for (MGLOfflinePack *pack in packs) { + NSDictionary *userInfo = [NSKeyedUnarchiver unarchiveObjectWithData:pack.context]; + if (userInfo[@"name"] == packName) { + tempPack = pack; + break; + } + } + + if (tempPack == nil) { + return callback(@[[NSNull null]]); + } + + NSDictionary *userInfo = [NSKeyedUnarchiver unarchiveObjectWithData:tempPack.context]; + + [[MGLOfflineStorage sharedOfflineStorage] removePack:tempPack withCompletionHandler:^(NSError * _Nullable error) { + if (error != nil) { + RCTLogError(@"Error: %@", error.localizedFailureReason); + } else { + NSMutableDictionary *deletedObject = [NSMutableDictionary new]; + [deletedObject setObject:userInfo[@"name"] forKey:@"deleted"]; + callback(@[[NSNull null], deletedObject]); + } + }]; + } + }]; +} + + + RCT_EXPORT_METHOD(setZoomLevelAnimated:(nonnull NSNumber *)reactTag zoomLevel:(double)zoomLevel) { @@ -331,7 +458,7 @@ RCT_EXPORT_METHOD(addAnnotations:(nonnull NSNumber *)reactTag }]; } -NSObject *convertToMGLAnnotation (NSObject *annotationObject) +NSObject *convertToMGLAnnotation (NSDictionary *annotationObject) { if (![annotationObject valueForKey:@"type"]) { RCTLogError(@"type point, polyline or polygon required"); diff --git a/ios/example.js b/ios/example.js index 4aa9556..9e6b912 100644 --- a/ios/example.js +++ b/ios/example.js @@ -7,7 +7,7 @@ var { AppRegistry, StyleSheet, Text, - StatusBarIOS, + StatusBar, View } = React; @@ -85,8 +85,11 @@ var MapExample = React.createClass({ onTap(location) { console.log('tapped', location); }, - render: function() { - StatusBarIOS.setHidden(true); + onOfflineProgressDidChange(progress) { + console.log(progress); + }, + render() { + StatusBar.setHidden(true); return ( this.setDirectionAnimated(mapRef, 0)}> @@ -145,18 +148,47 @@ var MapExample = React.createClass({ this.setUserTrackingMode(mapRef, this.userTrackingMode.follow)}> Set userTrackingMode to follow - this.getCenterCoordinateZoomLevel(mapRef, (err, location)=> { - if (err) console.log(err); + this.getCenterCoordinateZoomLevel(mapRef, (location)=> { console.log(location); })}> Get location - this.getDirection(mapRef, (err, direction)=> { - if (err) console.log(err); + this.getDirection(mapRef, (direction)=> { console.log(direction); })}> Get direction + this.getBounds(mapRef, (bounds)=> { + console.log(bounds); + })}> + Get bounds + + this.addPackForRegion(mapRef, { + name: 'test', + type: 'bbox', + bounds: [0, 0, 0, 0], + minZoomLevel: 0, + maxZoomLevel: 0, + styleURL: this.mapStyles.emerald + })}> + Create offline pack + + this.getPacks(mapRef, (err, packs)=> { + if (err) console.log(err); + console.log(packs); + })}> + Get offline packs + + this.removePack(mapRef, 'test', (err, info)=> { + if (err) console.log(err); + if (info) { + console.log('Deleted', info.deleted); + } else { + console.log('No packs to delete'); + } + })}> + Remove pack with name 'test' +