From f7043699b04e2fb29a892d14027abc99a8cf5af5 Mon Sep 17 00:00:00 2001 From: Jason Gaare Date: Mon, 24 Jul 2017 11:15:27 -0700 Subject: [PATCH] Use Apple's significant-change API (for iOS 11 UX) Summary: In the yet-to-be-released iOS 11, Apple has changed the way they notify the user of location services. (You can watch their session from WWDC about all the changes [here](https://developer.apple.com/videos/play/wwdc2017/713/).) The current implementation of `RCTLocationObserver` uses the standard location services from Apple. When the user has granted `Always` location permission and the application uses the background location service, the user is presented with the *_Blue Bar of Shame_* (for more information check out [this blog post](https://blog.set.gl/ios-11-location-permissions-and-avoiding-the-blue-bar-of-shame-1cee6cd93bbe)): ![image](https://user-images.githubusercontent.com/15896334/28285133-281e425c-6af9-11e7-9177-61b879ab593c.png) * Added `useSignificantChanges` boolean to the options passed. * Added `_usingSignificantChanges` boolean based on user options. If `true`, then the CLLocationManager will use functions `startMonitoringSignificantLocationChanges`/ `stopMonitoringSignificantLocationChanges` rather than the standard location services. * Changed method signature of `beginLocationUpdatesWithDesiredAccuracy` to include `useSignificantChanges` flag * Added check for new `NSLocationAlwaysAndWhenInUseUsageDescription` All unit tests passed. Tested in simulator and on device, toggling `useSignificantChanges` option when calling `watchPosition`. Results were as expected. **When `TRUE`, the _Blue Bar of Shame_ was not present.** Changes do not affect Android and location services still work as expected on Android. * Change is for iOS only * Using a different API will have different accuracy results. Adding `useSignificantChanges` as an option was by design so apps that want to have most accurate and most frequent update can still use standard location services. Closes https://github.com/facebook/react-native/pull/15062 Differential Revision: D5443331 Pulled By: javache fbshipit-source-id: 0cf5b6cd831c5a7c8c25a5ddc2e410a9aa989bf4 --- Libraries/Geolocation/Geolocation.js | 9 +++-- Libraries/Geolocation/RCTLocationObserver.m | 43 +++++++++++++++------ 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/Libraries/Geolocation/Geolocation.js b/Libraries/Geolocation/Geolocation.js index 4f49ba9bf..251666f4b 100644 --- a/Libraries/Geolocation/Geolocation.js +++ b/Libraries/Geolocation/Geolocation.js @@ -27,10 +27,11 @@ var subscriptions = []; var updatesEnabled = false; type GeoOptions = { - timeout: number, - maximumAge: number, - enableHighAccuracy: bool, + timeout?: number, + maximumAge?: number, + enableHighAccuracy?: bool, distanceFilter: number, + useSignificantChanges?: bool, } /** @@ -124,7 +125,7 @@ var Geolocation = { /* * Invokes the success callback whenever the location changes. Supported - * options: timeout (ms), maximumAge (ms), enableHighAccuracy (bool), distanceFilter(m) + * options: timeout (ms), maximumAge (ms), enableHighAccuracy (bool), distanceFilter(m), useSignificantChanges (bool) */ watchPosition: function(success: Function, error?: Function, options?: GeoOptions): number { if (!updatesEnabled) { diff --git a/Libraries/Geolocation/RCTLocationObserver.m b/Libraries/Geolocation/RCTLocationObserver.m index d442ad754..f30a7b9dd 100644 --- a/Libraries/Geolocation/RCTLocationObserver.m +++ b/Libraries/Geolocation/RCTLocationObserver.m @@ -32,6 +32,7 @@ typedef struct { double maximumAge; double accuracy; double distanceFilter; + BOOL useSignificantChanges; } RCTLocationOptions; @implementation RCTConvert (RCTLocationOptions) @@ -47,7 +48,8 @@ typedef struct { .timeout = [RCTConvert NSTimeInterval:options[@"timeout"]] ?: INFINITY, .maximumAge = [RCTConvert NSTimeInterval:options[@"maximumAge"]] ?: INFINITY, .accuracy = [RCTConvert BOOL:options[@"enableHighAccuracy"]] ? kCLLocationAccuracyBest : RCT_DEFAULT_LOCATION_ACCURACY, - .distanceFilter = distanceFilter + .distanceFilter = distanceFilter, + .useSignificantChanges = [RCTConvert BOOL:options[@"useSignificantChanges"]] ?: NO, }; } @@ -108,6 +110,7 @@ static NSDictionary *RCTPositionError(RCTPositionErrorCode code, NSDictionary *_lastLocationEvent; NSMutableArray *_pendingRequests; BOOL _observingLocation; + BOOL _usingSignificantChanges; RCTLocationOptions _observerOptions; } @@ -117,7 +120,10 @@ RCT_EXPORT_MODULE() - (void)dealloc { - [_locationManager stopUpdatingLocation]; + _usingSignificantChanges ? + [_locationManager stopMonitoringSignificantLocationChanges] : + [_locationManager stopUpdatingLocation]; + _locationManager.delegate = nil; } @@ -133,14 +139,18 @@ RCT_EXPORT_MODULE() #pragma mark - Private API -- (void)beginLocationUpdatesWithDesiredAccuracy:(CLLocationAccuracy)desiredAccuracy distanceFilter:(CLLocationDistance)distanceFilter +- (void)beginLocationUpdatesWithDesiredAccuracy:(CLLocationAccuracy)desiredAccuracy distanceFilter:(CLLocationDistance)distanceFilter useSignificantChanges:(BOOL)useSignificantChanges { [self requestAuthorization]; _locationManager.distanceFilter = distanceFilter; _locationManager.desiredAccuracy = desiredAccuracy; + _usingSignificantChanges = useSignificantChanges; + // Start observing location - [_locationManager startUpdatingLocation]; + _usingSignificantChanges ? + [_locationManager startMonitoringSignificantLocationChanges] : + [_locationManager startUpdatingLocation]; } #pragma mark - Timeout handler @@ -154,7 +164,9 @@ RCT_EXPORT_MODULE() // Stop updating if no pending requests if (_pendingRequests.count == 0 && !_observingLocation) { - [_locationManager stopUpdatingLocation]; + _usingSignificantChanges ? + [_locationManager stopMonitoringSignificantLocationChanges] : + [_locationManager stopUpdatingLocation]; } } @@ -195,7 +207,9 @@ RCT_EXPORT_METHOD(startObserving:(RCTLocationOptions)options) _observerOptions.accuracy = MIN(_observerOptions.accuracy, request.options.accuracy); } - [self beginLocationUpdatesWithDesiredAccuracy:_observerOptions.accuracy distanceFilter:_observerOptions.distanceFilter]; + [self beginLocationUpdatesWithDesiredAccuracy:_observerOptions.accuracy + distanceFilter:_observerOptions.distanceFilter + useSignificantChanges:_observerOptions.useSignificantChanges]; _observingLocation = YES; } @@ -206,7 +220,9 @@ RCT_EXPORT_METHOD(stopObserving) // Stop updating if no pending requests if (_pendingRequests.count == 0) { - [_locationManager stopUpdatingLocation]; + _usingSignificantChanges ? + [_locationManager stopMonitoringSignificantLocationChanges] : + [_locationManager stopUpdatingLocation]; } } @@ -269,7 +285,9 @@ RCT_EXPORT_METHOD(getCurrentPosition:(RCTLocationOptions)options if (_locationManager) { accuracy = MIN(_locationManager.desiredAccuracy, accuracy); } - [self beginLocationUpdatesWithDesiredAccuracy:accuracy distanceFilter:options.distanceFilter]; + [self beginLocationUpdatesWithDesiredAccuracy:accuracy + distanceFilter:options.distanceFilter + useSignificantChanges:options.useSignificantChanges]; } #pragma mark - CLLocationManagerDelegate @@ -306,7 +324,9 @@ RCT_EXPORT_METHOD(getCurrentPosition:(RCTLocationOptions)options // Stop updating if not observing if (!_observingLocation) { - [_locationManager stopUpdatingLocation]; + _usingSignificantChanges ? + [_locationManager stopMonitoringSignificantLocationChanges] : + [_locationManager stopUpdatingLocation]; } // Reset location accuracy if desiredAccuracy is changed. @@ -356,8 +376,9 @@ static void checkLocationConfig() { #if RCT_DEV if (!([[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationWhenInUseUsageDescription"] || - [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysUsageDescription"])) { - RCTLogError(@"Either NSLocationWhenInUseUsageDescription or NSLocationAlwaysUsageDescription key must be present in Info.plist to use geolocation."); + [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysUsageDescription"] || + [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysAndWhenInUseUsageDescription"])) { + RCTLogError(@"Either NSLocationWhenInUseUsageDescription or NSLocationAlwaysUsageDescription or NSLocationAlwaysAndWhenInUseUsageDescription key must be present in Info.plist to use geolocation."); } #endif }