2015-03-23 20:28:42 +00:00
/ * *
* Copyright ( c ) 2015 - present , Facebook , Inc .
*
2018-02-17 02:24:55 +00:00
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree .
2015-03-23 20:28:42 +00:00
* /
2015-03-09 10:04:44 +00:00
# import "RCTLocationObserver.h"
# import < CoreLocation / CLError . h >
# import < CoreLocation / CLLocationManager . h >
# import < CoreLocation / CLLocationManagerDelegate . h >
2016-11-23 15:47:52 +00:00
# import < React / RCTAssert . h >
# import < React / RCTBridge . h >
# import < React / RCTConvert . h >
# import < React / RCTEventDispatcher . h >
# import < React / RCTLog . h >
2015-03-09 10:04:44 +00:00
typedef NS_ENUM ( NSInteger , RCTPositionErrorCode ) {
RCTPositionErrorDenied = 1 ,
RCTPositionErrorUnavailable ,
RCTPositionErrorTimeout ,
} ;
# define RCT_DEFAULT _LOCATION _ACCURACY kCLLocationAccuracyHundredMeters
2017-08-29 11:00:41 +00:00
typedef struct {
BOOL skipPermissionRequests ;
} RCTLocationConfiguration ;
2015-03-09 10:04:44 +00:00
typedef struct {
2015-05-02 21:11:09 +00:00
double timeout ;
double maximumAge ;
double accuracy ;
2016-02-06 00:54:14 +00:00
double distanceFilter ;
2017-07-24 18:15:27 +00:00
BOOL useSignificantChanges ;
2015-03-09 10:04:44 +00:00
} RCTLocationOptions ;
2015-04-18 17:43:20 +00:00
@ implementation RCTConvert ( RCTLocationOptions )
2017-08-29 11:00:41 +00:00
+ ( RCTLocationConfiguration ) RCTLocationConfiguration : ( id ) json
{
NSDictionary < NSString * , id > * options = [ RCTConvert NSDictionary : json ] ;
return ( RCTLocationConfiguration ) {
. skipPermissionRequests = [ RCTConvert BOOL : options [ @ "skipPermissionRequests" ] ]
} ;
}
2015-04-18 17:43:20 +00:00
+ ( RCTLocationOptions ) RCTLocationOptions : ( id ) json
2015-03-09 10:04:44 +00:00
{
2015-11-14 18:25:00 +00:00
NSDictionary < NSString * , id > * options = [ RCTConvert NSDictionary : json ] ;
2016-02-06 00:54:14 +00:00
double distanceFilter = options [ @ "distanceFilter" ] = = NULL ? RCT_DEFAULT _LOCATION _ACCURACY
: [ RCTConvert double : options [ @ "distanceFilter" ] ] ? : kCLDistanceFilterNone ;
2015-03-09 10:04:44 +00:00
return ( RCTLocationOptions ) {
. timeout = [ RCTConvert NSTimeInterval : options [ @ "timeout" ] ] ? : INFINITY ,
. maximumAge = [ RCTConvert NSTimeInterval : options [ @ "maximumAge" ] ] ? : INFINITY ,
2016-02-06 00:54:14 +00:00
. accuracy = [ RCTConvert BOOL : options [ @ "enableHighAccuracy" ] ] ? kCLLocationAccuracyBest : RCT_DEFAULT _LOCATION _ACCURACY ,
2017-07-24 18:15:27 +00:00
. distanceFilter = distanceFilter ,
. useSignificantChanges = [ RCTConvert BOOL : options [ @ "useSignificantChanges" ] ] ? : NO ,
2015-03-09 10:04:44 +00:00
} ;
}
2015-04-18 17:43:20 +00:00
@ end
2015-11-14 18:25:00 +00:00
static NSDictionary < NSString * , id > * RCTPositionError ( RCTPositionErrorCode code , NSString * msg / * nil for default * / )
2015-03-09 10:04:44 +00:00
{
if ( ! msg ) {
switch ( code ) {
case RCTPositionErrorDenied :
msg = @ "User denied access to location services." ;
break ;
case RCTPositionErrorUnavailable :
msg = @ "Unable to retrieve location." ;
break ;
case RCTPositionErrorTimeout :
msg = @ "The location request timed out." ;
break ;
}
}
return @ {
@ "code" : @ ( code ) ,
@ "message" : msg ,
@ "PERMISSION_DENIED" : @ ( RCTPositionErrorDenied ) ,
@ "POSITION_UNAVAILABLE" : @ ( RCTPositionErrorUnavailable ) ,
@ "TIMEOUT" : @ ( RCTPositionErrorTimeout )
} ;
}
@ interface RCTLocationRequest : NSObject
@ property ( nonatomic , copy ) RCTResponseSenderBlock successBlock ;
@ property ( nonatomic , copy ) RCTResponseSenderBlock errorBlock ;
@ property ( nonatomic , assign ) RCTLocationOptions options ;
@ property ( nonatomic , strong ) NSTimer * timeoutTimer ;
@ end
@ implementation RCTLocationRequest
- ( void ) dealloc
{
2015-05-26 21:54:32 +00:00
if ( _timeoutTimer . valid ) {
[ _timeoutTimer invalidate ] ;
}
2015-03-09 10:04:44 +00:00
}
@ end
@ interface RCTLocationObserver ( ) < CLLocationManagerDelegate >
@ end
@ implementation RCTLocationObserver
{
CLLocationManager * _locationManager ;
2015-11-14 18:25:00 +00:00
NSDictionary < NSString * , id > * _lastLocationEvent ;
2015-11-03 22:45:46 +00:00
NSMutableArray < RCTLocationRequest * > * _pendingRequests ;
2015-03-09 10:04:44 +00:00
BOOL _observingLocation ;
2017-07-24 18:15:27 +00:00
BOOL _usingSignificantChanges ;
2017-08-29 11:00:41 +00:00
RCTLocationConfiguration _locationConfiguration ;
2015-03-09 10:04:44 +00:00
RCTLocationOptions _observerOptions ;
}
2015-04-08 12:42:43 +00:00
RCT_EXPORT _MODULE ( )
2015-03-09 10:04:44 +00:00
# pragma mark - Lifecycle
- ( void ) dealloc
{
2017-07-24 18:15:27 +00:00
_usingSignificantChanges ?
[ _locationManager stopMonitoringSignificantLocationChanges ] :
[ _locationManager stopUpdatingLocation ] ;
2015-04-18 17:43:20 +00:00
_locationManager . delegate = nil ;
2015-03-09 10:04:44 +00:00
}
2015-04-20 19:06:02 +00:00
- ( dispatch_queue _t ) methodQueue
{
return dispatch_get _main _queue ( ) ;
}
2016-05-25 11:17:35 +00:00
- ( NSArray < NSString * > * ) supportedEvents
{
return @ [ @ "geolocationDidChange" , @ "geolocationError" ] ;
}
2016-05-04 17:33:58 +00:00
2016-05-25 11:17:35 +00:00
# pragma mark - Private API
2016-05-24 19:33:57 +00:00
2017-07-24 18:15:27 +00:00
- ( void ) beginLocationUpdatesWithDesiredAccuracy : ( CLLocationAccuracy ) desiredAccuracy distanceFilter : ( CLLocationDistance ) distanceFilter useSignificantChanges : ( BOOL ) useSignificantChanges
2015-03-09 10:04:44 +00:00
{
2017-08-29 11:00:41 +00:00
if ( ! _locationConfiguration . skipPermissionRequests ) {
[ self requestAuthorization ] ;
}
2018-01-13 02:48:10 +00:00
if ( ! _locationManager ) {
_locationManager = [ CLLocationManager new ] ;
_locationManager . delegate = self ;
}
2015-03-09 10:04:44 +00:00
2016-05-04 17:33:58 +00:00
_locationManager . distanceFilter = distanceFilter ;
2016-03-08 13:16:32 +00:00
_locationManager . desiredAccuracy = desiredAccuracy ;
2017-07-24 18:15:27 +00:00
_usingSignificantChanges = useSignificantChanges ;
2015-03-09 10:04:44 +00:00
// Start observing location
2017-07-24 18:15:27 +00:00
_usingSignificantChanges ?
[ _locationManager startMonitoringSignificantLocationChanges ] :
[ _locationManager startUpdatingLocation ] ;
2015-03-09 10:04:44 +00:00
}
# pragma mark - Timeout handler
- ( void ) timeout : ( NSTimer * ) timer
{
RCTLocationRequest * request = timer . userInfo ;
2016-09-21 21:27:49 +00:00
NSString * message = [ NSString stringWithFormat : @ "Unable to fetch location within %.1fs." , request . options . timeout ] ;
2015-03-09 10:04:44 +00:00
request . errorBlock ( @ [ RCTPositionError ( RCTPositionErrorTimeout , message ) ] ) ;
[ _pendingRequests removeObject : request ] ;
// Stop updating if no pending requests
if ( _pendingRequests . count = = 0 && ! _observingLocation ) {
2017-07-24 18:15:27 +00:00
_usingSignificantChanges ?
[ _locationManager stopMonitoringSignificantLocationChanges ] :
[ _locationManager stopUpdatingLocation ] ;
2015-03-09 10:04:44 +00:00
}
}
# pragma mark - Public API
2017-08-29 11:00:41 +00:00
RCT_EXPORT _METHOD ( setConfiguration : ( RCTLocationConfiguration ) config )
{
_locationConfiguration = config ;
}
2017-05-25 14:01:17 +00:00
RCT_EXPORT _METHOD ( requestAuthorization )
{
if ( ! _locationManager ) {
_locationManager = [ CLLocationManager new ] ;
_locationManager . delegate = self ;
}
// Request location access permission
if ( [ [ NSBundle mainBundle ] objectForInfoDictionaryKey : @ "NSLocationAlwaysUsageDescription" ] &&
[ _locationManager respondsToSelector : @ selector ( requestAlwaysAuthorization ) ] ) {
[ _locationManager requestAlwaysAuthorization ] ;
// On iOS 9 + we also need to enable background updates
NSArray * backgroundModes = [ [ NSBundle mainBundle ] objectForInfoDictionaryKey : @ "UIBackgroundModes" ] ;
if ( backgroundModes && [ backgroundModes containsObject : @ "location" ] ) {
if ( [ _locationManager respondsToSelector : @ selector ( setAllowsBackgroundLocationUpdates : ) ] ) {
[ _locationManager setAllowsBackgroundLocationUpdates : YES ] ;
}
}
} else if ( [ [ NSBundle mainBundle ] objectForInfoDictionaryKey : @ "NSLocationWhenInUseUsageDescription" ] &&
[ _locationManager respondsToSelector : @ selector ( requestWhenInUseAuthorization ) ] ) {
[ _locationManager requestWhenInUseAuthorization ] ;
}
}
2015-04-27 10:58:30 +00:00
RCT_EXPORT _METHOD ( startObserving : ( RCTLocationOptions ) options )
2015-03-09 10:04:44 +00:00
{
2016-08-18 14:16:26 +00:00
checkLocationConfig ( ) ;
2015-04-09 04:47:06 +00:00
2015-04-18 17:43:20 +00:00
// Select best options
2015-04-27 10:58:30 +00:00
_observerOptions = options ;
2015-04-18 17:43:20 +00:00
for ( RCTLocationRequest * request in _pendingRequests ) {
_observerOptions . accuracy = MIN ( _observerOptions . accuracy , request . options . accuracy ) ;
}
2015-03-09 10:04:44 +00:00
2017-07-24 18:15:27 +00:00
[ self beginLocationUpdatesWithDesiredAccuracy : _observerOptions . accuracy
distanceFilter : _observerOptions . distanceFilter
useSignificantChanges : _observerOptions . useSignificantChanges ] ;
2015-04-18 17:43:20 +00:00
_observingLocation = YES ;
2015-03-09 10:04:44 +00:00
}
2015-04-08 15:52:48 +00:00
RCT_EXPORT _METHOD ( stopObserving )
2015-03-09 10:04:44 +00:00
{
2015-04-18 17:43:20 +00:00
// Stop observing
_observingLocation = NO ;
2015-03-09 10:04:44 +00:00
2015-04-18 17:43:20 +00:00
// Stop updating if no pending requests
if ( _pendingRequests . count = = 0 ) {
2017-07-24 18:15:27 +00:00
_usingSignificantChanges ?
[ _locationManager stopMonitoringSignificantLocationChanges ] :
[ _locationManager stopUpdatingLocation ] ;
2015-04-18 17:43:20 +00:00
}
2015-03-09 10:04:44 +00:00
}
2015-04-27 10:58:30 +00:00
RCT_EXPORT _METHOD ( getCurrentPosition : ( RCTLocationOptions ) options
2015-04-08 15:52:48 +00:00
withSuccessCallback : ( RCTResponseSenderBlock ) successBlock
errorCallback : ( RCTResponseSenderBlock ) errorBlock )
2015-03-09 10:04:44 +00:00
{
2016-08-18 14:16:26 +00:00
checkLocationConfig ( ) ;
2015-04-09 04:47:06 +00:00
2015-03-09 10:04:44 +00:00
if ( ! successBlock ) {
RCTLogError ( @ "%@.getCurrentPosition called with nil success parameter." , [ self class ] ) ;
return ;
}
2015-04-18 17:43:20 +00:00
if ( ! [ CLLocationManager locationServicesEnabled ] ) {
if ( errorBlock ) {
errorBlock ( @ [
RCTPositionError ( RCTPositionErrorUnavailable , @ "Location services disabled." )
] ) ;
return ;
2015-03-09 10:04:44 +00:00
}
2015-04-18 17:43:20 +00:00
}
2015-03-09 10:04:44 +00:00
2015-04-18 17:43:20 +00:00
if ( [ CLLocationManager authorizationStatus ] = = kCLAuthorizationStatusDenied ) {
if ( errorBlock ) {
errorBlock ( @ [
RCTPositionError ( RCTPositionErrorDenied , nil )
] ) ;
return ;
2015-03-09 10:04:44 +00:00
}
2015-04-18 17:43:20 +00:00
}
2015-03-09 10:04:44 +00:00
2015-04-18 17:43:20 +00:00
// Check if previous recorded location exists and is good enough
if ( _lastLocationEvent &&
2016-03-08 13:16:32 +00:00
[ NSDate date ] . timeIntervalSince1970 - [ RCTConvert NSTimeInterval : _lastLocationEvent [ @ "timestamp" ] ] < options . maximumAge &&
[ _lastLocationEvent [ @ "coords" ] [ @ "accuracy" ] doubleValue ] <= options . accuracy ) {
2015-03-09 10:04:44 +00:00
2015-04-18 17:43:20 +00:00
// Call success block with most recent known location
successBlock ( @ [ _lastLocationEvent ] ) ;
return ;
}
2015-03-09 10:04:44 +00:00
2015-04-18 17:43:20 +00:00
// Create request
2015-08-17 14:35:34 +00:00
RCTLocationRequest * request = [ RCTLocationRequest new ] ;
2015-04-18 17:43:20 +00:00
request . successBlock = successBlock ;
request . errorBlock = errorBlock ? : ^ ( NSArray * args ) { } ;
request . options = options ;
request . timeoutTimer = [ NSTimer scheduledTimerWithTimeInterval : options . timeout
target : self
selector : @ selector ( timeout : )
userInfo : request
repeats : NO ] ;
2015-12-16 16:37:00 +00:00
if ( ! _pendingRequests ) {
_pendingRequests = [ NSMutableArray new ] ;
}
2015-04-18 17:43:20 +00:00
[ _pendingRequests addObject : request ] ;
// Configure location manager and begin updating location
2016-03-08 13:16:32 +00:00
CLLocationAccuracy accuracy = options . accuracy ;
if ( _locationManager ) {
accuracy = MIN ( _locationManager . desiredAccuracy , accuracy ) ;
}
2017-07-24 18:15:27 +00:00
[ self beginLocationUpdatesWithDesiredAccuracy : accuracy
distanceFilter : options . distanceFilter
useSignificantChanges : options . useSignificantChanges ] ;
2015-03-09 10:04:44 +00:00
}
# pragma mark - CLLocationManagerDelegate
2015-11-03 22:45:46 +00:00
- ( void ) locationManager : ( CLLocationManager * ) manager
didUpdateLocations : ( NSArray < CLLocation * > * ) locations
2015-03-09 10:04:44 +00:00
{
// Create event
2015-08-24 10:14:33 +00:00
CLLocation * location = locations . lastObject ;
2015-03-09 10:04:44 +00:00
_lastLocationEvent = @ {
@ "coords" : @ {
@ "latitude" : @ ( location . coordinate . latitude ) ,
@ "longitude" : @ ( location . coordinate . longitude ) ,
@ "altitude" : @ ( location . altitude ) ,
@ "accuracy" : @ ( location . horizontalAccuracy ) ,
@ "altitudeAccuracy" : @ ( location . verticalAccuracy ) ,
@ "heading" : @ ( location . course ) ,
@ "speed" : @ ( location . speed ) ,
} ,
2016-02-25 15:22:18 +00:00
@ "timestamp" : @ ( [ location . timestamp timeIntervalSince1970 ] * 1000 ) // in ms
2015-03-09 10:04:44 +00:00
} ;
// Send event
if ( _observingLocation ) {
2016-05-25 11:17:35 +00:00
[ self sendEventWithName : @ "geolocationDidChange" body : _lastLocationEvent ] ;
2015-03-09 10:04:44 +00:00
}
// Fire all queued callbacks
for ( RCTLocationRequest * request in _pendingRequests ) {
request . successBlock ( @ [ _lastLocationEvent ] ) ;
2015-05-26 21:54:32 +00:00
[ request . timeoutTimer invalidate ] ;
2015-03-09 10:04:44 +00:00
}
[ _pendingRequests removeAllObjects ] ;
2016-03-08 13:16:32 +00:00
// Stop updating if not observing
2015-03-09 10:04:44 +00:00
if ( ! _observingLocation ) {
2017-07-24 18:15:27 +00:00
_usingSignificantChanges ?
[ _locationManager stopMonitoringSignificantLocationChanges ] :
[ _locationManager stopUpdatingLocation ] ;
2015-03-09 10:04:44 +00:00
}
2015-10-12 18:54:30 +00:00
// Reset location accuracy if desiredAccuracy is changed .
// Otherwise update accuracy will force triggering didUpdateLocations , watchPosition would keeping receiving location updates , even there ' s no location changes .
if ( ABS ( _locationManager . desiredAccuracy - RCT_DEFAULT _LOCATION _ACCURACY ) > 0.000001 ) {
_locationManager . desiredAccuracy = RCT_DEFAULT _LOCATION _ACCURACY ;
}
2015-03-09 10:04:44 +00:00
}
- ( void ) locationManager : ( CLLocationManager * ) manager didFailWithError : ( NSError * ) error
{
// Check error type
2015-11-14 18:25:00 +00:00
NSDictionary < NSString * , id > * jsError = nil ;
2015-03-09 10:04:44 +00:00
switch ( error . code ) {
case kCLErrorDenied :
jsError = RCTPositionError ( RCTPositionErrorDenied , nil ) ;
break ;
case kCLErrorNetwork :
jsError = RCTPositionError ( RCTPositionErrorUnavailable , @ "Unable to retrieve location due to a network failure" ) ;
break ;
case kCLErrorLocationUnknown :
default :
jsError = RCTPositionError ( RCTPositionErrorUnavailable , nil ) ;
break ;
}
// Send event
if ( _observingLocation ) {
2016-05-25 11:17:35 +00:00
[ self sendEventWithName : @ "geolocationError" body : jsError ] ;
2015-03-09 10:04:44 +00:00
}
// Fire all queued error callbacks
for ( RCTLocationRequest * request in _pendingRequests ) {
request . errorBlock ( @ [ jsError ] ) ;
2015-05-26 21:54:32 +00:00
[ request . timeoutTimer invalidate ] ;
2015-03-09 10:04:44 +00:00
}
[ _pendingRequests removeAllObjects ] ;
2015-10-12 18:54:30 +00:00
// Reset location accuracy if desiredAccuracy is changed .
// Otherwise update accuracy will force triggering didUpdateLocations , watchPosition would keeping receiving location updates , even there ' s no location changes .
if ( ABS ( _locationManager . desiredAccuracy - RCT_DEFAULT _LOCATION _ACCURACY ) > 0.000001 ) {
_locationManager . desiredAccuracy = RCT_DEFAULT _LOCATION _ACCURACY ;
}
2015-03-09 10:04:44 +00:00
}
2016-08-18 14:16:26 +00:00
static void checkLocationConfig ( )
2015-04-09 04:47:06 +00:00
{
2016-08-18 14:16:26 +00:00
# if RCT_DEV
2016-01-12 11:11:00 +00:00
if ( ! ( [ [ NSBundle mainBundle ] objectForInfoDictionaryKey : @ "NSLocationWhenInUseUsageDescription" ] ||
2017-07-24 18:15:27 +00:00
[ [ 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." ) ;
2015-04-09 04:47:06 +00:00
}
2016-08-18 14:16:26 +00:00
# endif
2015-04-09 04:47:06 +00:00
}
2015-03-09 10:04:44 +00:00
@ end