Improved Geolocation API
This commit is contained in:
parent
c5e6f550ad
commit
705a8e0144
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*
|
||||
* @providesModule GeoLocationExample
|
||||
* @providesModule GeolocationExample
|
||||
*/
|
||||
/* eslint no-console: 0 */
|
||||
'use strict';
|
||||
|
@ -15,19 +15,19 @@ var {
|
|||
} = React;
|
||||
|
||||
exports.framework = 'React';
|
||||
exports.title = 'GeoLocation';
|
||||
exports.description = 'Examples of using the GeoLocation API.';
|
||||
exports.title = 'Geolocation';
|
||||
exports.description = 'Examples of using the Geolocation API.';
|
||||
|
||||
exports.examples = [
|
||||
{
|
||||
title: 'navigator.geolocation',
|
||||
render: function() {
|
||||
return <GeoLocationExample />;
|
||||
return <GeolocationExample />;
|
||||
},
|
||||
}
|
||||
];
|
||||
|
||||
var GeoLocationExample = React.createClass({
|
||||
var GeolocationExample = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
initialPosition: 'unknown',
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
134180011AA9153C003F314A /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13417FEF1AA914B8003F314A /* libRCTText.a */; };
|
||||
134180021AA9153C003F314A /* libReactKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13417FFF1AA91531003F314A /* libReactKit.a */; };
|
||||
1341802C1AA9178B003F314A /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1341802B1AA91779003F314A /* libRCTNetwork.a */; };
|
||||
134A8A2A1AACED7A00945AAE /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 134A8A251AACED6A00945AAE /* libRCTGeolocation.a */; };
|
||||
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; };
|
||||
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; };
|
||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
|
||||
|
@ -46,6 +47,13 @@
|
|||
remoteGlobalIDString = 58B511DB1A9E6C8500147676;
|
||||
remoteInfo = RCTNetwork;
|
||||
};
|
||||
134A8A241AACED6A00945AAE /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = 134814201AA4EA6300B7C361;
|
||||
remoteInfo = RCTGeolocation;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
|
@ -53,6 +61,7 @@
|
|||
13417FEA1AA914B8003F314A /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = ../../Libraries/Text/RCTText.xcodeproj; sourceTree = "<group>"; };
|
||||
13417FFA1AA91531003F314A /* ReactKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReactKit.xcodeproj; path = ../../ReactKit/ReactKit.xcodeproj; sourceTree = "<group>"; };
|
||||
134180261AA91779003F314A /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = ../../Libraries/Network/RCTNetwork.xcodeproj; sourceTree = "<group>"; };
|
||||
134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = ../../Libraries/Geolocation/RCTGeolocation.xcodeproj; sourceTree = "<group>"; };
|
||||
13B07F961A680F5B00A75B9A /* UIExplorer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = UIExplorer.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
|
||||
13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
|
||||
|
@ -67,6 +76,7 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
134A8A2A1AACED7A00945AAE /* libRCTGeolocation.a in Frameworks */,
|
||||
1341802C1AA9178B003F314A /* libRCTNetwork.a in Frameworks */,
|
||||
134180011AA9153C003F314A /* libRCTText.a in Frameworks */,
|
||||
134180021AA9153C003F314A /* libReactKit.a in Frameworks */,
|
||||
|
@ -80,6 +90,7 @@
|
|||
1316A21D1AA397F400C0188E /* Libraries */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */,
|
||||
13417FFA1AA91531003F314A /* ReactKit.xcodeproj */,
|
||||
134180261AA91779003F314A /* RCTNetwork.xcodeproj */,
|
||||
13417FEA1AA914B8003F314A /* RCTText.xcodeproj */,
|
||||
|
@ -120,6 +131,14 @@
|
|||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
134A8A211AACED6A00945AAE /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
134A8A251AACED6A00945AAE /* libRCTGeolocation.a */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
13B07FAE1A68108700A75B9A /* UIExplorer */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -191,6 +210,10 @@
|
|||
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectReferences = (
|
||||
{
|
||||
ProductGroup = 134A8A211AACED6A00945AAE /* Products */;
|
||||
ProjectRef = 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */;
|
||||
},
|
||||
{
|
||||
ProductGroup = 13417FE41AA91428003F314A /* Products */;
|
||||
ProjectRef = 13417FE31AA91428003F314A /* RCTImage.xcodeproj */;
|
||||
|
@ -244,6 +267,13 @@
|
|||
remoteRef = 1341802A1AA91779003F314A /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
134A8A251AACED6A00945AAE /* libRCTGeolocation.a */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = archive.ar;
|
||||
path = libRCTGeolocation.a;
|
||||
remoteRef = 134A8A241AACED6A00945AAE /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
/* End PBXReferenceProxy section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
|
|
|
@ -32,7 +32,7 @@ var EXAMPLES = [
|
|||
require('./ActivityIndicatorExample'),
|
||||
require('./ScrollViewExample'),
|
||||
require('./DatePickerExample'),
|
||||
require('./GeoLocationExample'),
|
||||
require('./GeolocationExample'),
|
||||
require('./TabBarExample'),
|
||||
];
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*
|
||||
* @providesModule GeoLocation
|
||||
* @providesModule Geolocation
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
|
||||
var RCTLocationObserver = require('NativeModules').RKLocationObserver;
|
||||
var RCTLocationObserver = require('NativeModulesDeprecated').RKLocationObserver;
|
||||
|
||||
var invariant = require('invariant');
|
||||
var logError = require('logError');
|
||||
|
@ -16,13 +16,6 @@ var subscriptions = [];
|
|||
|
||||
var updatesEnabled = false;
|
||||
|
||||
var ensureObserving = function() {
|
||||
if (!updatesEnabled) {
|
||||
RCTLocationObserver.startObserving();
|
||||
updatesEnabled = true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* /!\ ATTENTION /!\
|
||||
* You need to add NSLocationWhenInUseUsageDescription key
|
||||
|
@ -30,43 +23,51 @@ var ensureObserving = function() {
|
|||
* to *fail silently*!
|
||||
* \!/ \!/
|
||||
*
|
||||
* GeoLocation follows the MDN specification:
|
||||
* Geolocation follows the MDN specification:
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/Geolocation
|
||||
*/
|
||||
class GeoLocation {
|
||||
static getCurrentPosition(geo_success, geo_error, geo_options) {
|
||||
var Geolocation = {
|
||||
|
||||
getCurrentPosition: function(geo_success, geo_error, geo_options) {
|
||||
invariant(
|
||||
typeof geo_success === 'function',
|
||||
'Must provide a valid geo_success callback.'
|
||||
);
|
||||
if (geo_options) {
|
||||
warning('geo_options are not yet supported.');
|
||||
}
|
||||
ensureObserving();
|
||||
RCTLocationObserver.getCurrentPosition(
|
||||
geo_success,
|
||||
geo_error || logError
|
||||
geo_error || logError,
|
||||
geo_options || {}
|
||||
);
|
||||
}
|
||||
static watchPosition(callback) {
|
||||
ensureObserving();
|
||||
},
|
||||
|
||||
watchPosition: function(success, error, options) {
|
||||
if (!updatesEnabled) {
|
||||
RCTLocationObserver.startObserving(options || {});
|
||||
updatesEnabled = true;
|
||||
}
|
||||
var watchID = subscriptions.length;
|
||||
subscriptions.push(
|
||||
subscriptions.push([
|
||||
RCTDeviceEventEmitter.addListener(
|
||||
'geoLocationDidChange',
|
||||
callback
|
||||
)
|
||||
);
|
||||
'geolocationDidChange',
|
||||
success
|
||||
),
|
||||
error ? RCTDeviceEventEmitter.addListener(
|
||||
'geolocationError',
|
||||
error
|
||||
) : null,
|
||||
]);
|
||||
return watchID;
|
||||
}
|
||||
static clearWatch(watchID) {
|
||||
},
|
||||
|
||||
clearWatch: function(watchID) {
|
||||
var sub = subscriptions[watchID];
|
||||
if (!sub) {
|
||||
// Silently exit when the watchID is invalid or already cleared
|
||||
// This is consistent with timers
|
||||
return;
|
||||
}
|
||||
sub.remove();
|
||||
sub[0].remove();
|
||||
sub[1] && sub[1].remove();
|
||||
subscriptions[watchID] = undefined;
|
||||
var noWatchers = true;
|
||||
for (var ii = 0; ii < subscriptions.length; ii++) {
|
||||
|
@ -75,10 +76,11 @@ class GeoLocation {
|
|||
}
|
||||
}
|
||||
if (noWatchers) {
|
||||
GeoLocation.stopObserving();
|
||||
Geolocation.stopObserving();
|
||||
}
|
||||
}
|
||||
static stopObserving() {
|
||||
},
|
||||
|
||||
stopObserving: function() {
|
||||
if (updatesEnabled) {
|
||||
RCTLocationObserver.stopObserving();
|
||||
updatesEnabled = false;
|
||||
|
@ -89,10 +91,8 @@ class GeoLocation {
|
|||
}
|
||||
}
|
||||
subscriptions = [];
|
||||
} else {
|
||||
warning('Tried to stop observing when not observing.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GeoLocation;
|
||||
module.exports = Geolocation;
|
|
@ -0,0 +1,319 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import "RCTLocationObserver.h"
|
||||
|
||||
#import <CoreLocation/CLError.h>
|
||||
#import <CoreLocation/CLLocationManager.h>
|
||||
#import <CoreLocation/CLLocationManagerDelegate.h>
|
||||
|
||||
#import "RCTAssert.h"
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTEventDispatcher.h"
|
||||
#import "RCTLog.h"
|
||||
|
||||
typedef NS_ENUM(NSInteger, RCTPositionErrorCode) {
|
||||
RCTPositionErrorDenied = 1,
|
||||
RCTPositionErrorUnavailable,
|
||||
RCTPositionErrorTimeout,
|
||||
};
|
||||
|
||||
#define RCT_DEFAULT_LOCATION_ACCURACY kCLLocationAccuracyHundredMeters
|
||||
|
||||
typedef struct {
|
||||
NSTimeInterval timeout;
|
||||
NSTimeInterval maximumAge;
|
||||
CLLocationAccuracy accuracy;
|
||||
} RCTLocationOptions;
|
||||
|
||||
static RCTLocationOptions RCTLocationOptionsWithJSON(id json)
|
||||
{
|
||||
NSDictionary *options = [RCTConvert NSDictionary:json];
|
||||
return (RCTLocationOptions){
|
||||
.timeout = [RCTConvert NSTimeInterval:options[@"timeout"]] ?: INFINITY,
|
||||
.maximumAge = [RCTConvert NSTimeInterval:options[@"maximumAge"]] ?: INFINITY,
|
||||
.accuracy = [RCTConvert BOOL:options[@"enableHighAccuracy"]] ? kCLLocationAccuracyBest : RCT_DEFAULT_LOCATION_ACCURACY
|
||||
};
|
||||
}
|
||||
|
||||
static NSDictionary *RCTPositionError(RCTPositionErrorCode code, NSString *msg /* nil for default */)
|
||||
{
|
||||
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
|
||||
{
|
||||
[_timeoutTimer invalidate];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface RCTLocationObserver () <CLLocationManagerDelegate>
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTLocationObserver
|
||||
{
|
||||
CLLocationManager *_locationManager;
|
||||
NSDictionary *_lastLocationEvent;
|
||||
NSMutableArray *_pendingRequests;
|
||||
BOOL _observingLocation;
|
||||
RCTLocationOptions _observerOptions;
|
||||
}
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
|
||||
_locationManager = [[CLLocationManager alloc] init];
|
||||
_locationManager.distanceFilter = RCT_DEFAULT_LOCATION_ACCURACY;
|
||||
_locationManager.delegate = self;
|
||||
|
||||
_pendingRequests = [[NSMutableArray alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[_locationManager stopUpdatingLocation];
|
||||
}
|
||||
|
||||
#pragma mark - Private API
|
||||
|
||||
- (void)beginLocationUpdates
|
||||
{
|
||||
// Request location access permission
|
||||
if ([_locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) {
|
||||
[_locationManager requestWhenInUseAuthorization];
|
||||
}
|
||||
|
||||
// Start observing location
|
||||
[_locationManager startUpdatingLocation];
|
||||
}
|
||||
|
||||
#pragma mark - Timeout handler
|
||||
|
||||
- (void)timeout:(NSTimer *)timer
|
||||
{
|
||||
RCTLocationRequest *request = timer.userInfo;
|
||||
NSString *message = [NSString stringWithFormat: @"Unable to fetch location within %zds.", (NSInteger)(timer.timeInterval * 1000.0)];
|
||||
request.errorBlock(@[RCTPositionError(RCTPositionErrorTimeout, message)]);
|
||||
[_pendingRequests removeObject:request];
|
||||
|
||||
// Stop updating if no pending requests
|
||||
if (_pendingRequests.count == 0 && !_observingLocation) {
|
||||
[_locationManager stopUpdatingLocation];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Public API
|
||||
|
||||
- (void)startObserving:(NSDictionary *)optionsJSON
|
||||
{
|
||||
RCT_EXPORT();
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
|
||||
// Select best options
|
||||
_observerOptions = RCTLocationOptionsWithJSON(optionsJSON);
|
||||
for (RCTLocationRequest *request in _pendingRequests) {
|
||||
_observerOptions.accuracy = MIN(_observerOptions.accuracy, request.options.accuracy);
|
||||
}
|
||||
|
||||
_locationManager.desiredAccuracy = _observerOptions.accuracy;
|
||||
[self beginLocationUpdates];
|
||||
_observingLocation = YES;
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
- (void)stopObserving
|
||||
{
|
||||
RCT_EXPORT();
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
|
||||
// Stop observing
|
||||
_observingLocation = NO;
|
||||
|
||||
// Stop updating if no pending requests
|
||||
if (_pendingRequests.count == 0) {
|
||||
[_locationManager stopUpdatingLocation];
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
- (void)getCurrentPosition:(RCTResponseSenderBlock)successBlock
|
||||
withErrorCallback:(RCTResponseSenderBlock)errorBlock
|
||||
options:(NSDictionary *)optionsJSON
|
||||
{
|
||||
RCT_EXPORT();
|
||||
|
||||
if (!successBlock) {
|
||||
RCTLogError(@"%@.getCurrentPosition called with nil success parameter.", [self class]);
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
|
||||
if (![CLLocationManager locationServicesEnabled]) {
|
||||
if (errorBlock) {
|
||||
errorBlock(@[
|
||||
RCTPositionError(RCTPositionErrorUnavailable, @"Location services disabled.")
|
||||
]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (![CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied) {
|
||||
if (errorBlock) {
|
||||
errorBlock(@[
|
||||
RCTPositionError(RCTPositionErrorDenied, nil)
|
||||
]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Get options
|
||||
RCTLocationOptions options = RCTLocationOptionsWithJSON(optionsJSON);
|
||||
|
||||
// Check if previous recorded location exists and is good enough
|
||||
if (_lastLocationEvent &&
|
||||
CFAbsoluteTimeGetCurrent() - [RCTConvert NSTimeInterval:_lastLocationEvent[@"timestamp"]] < options.maximumAge &&
|
||||
[_lastLocationEvent[@"coords"][@"accuracy"] doubleValue] >= options.accuracy) {
|
||||
|
||||
// Call success block with most recent known location
|
||||
successBlock(@[_lastLocationEvent]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create request
|
||||
RCTLocationRequest *request = [[RCTLocationRequest alloc] init];
|
||||
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];
|
||||
[_pendingRequests addObject:request];
|
||||
|
||||
// Configure location manager and begin updating location
|
||||
_locationManager.desiredAccuracy = MIN(_locationManager.desiredAccuracy, options.accuracy);
|
||||
[self beginLocationUpdates];
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - CLLocationManagerDelegate
|
||||
|
||||
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
|
||||
{
|
||||
// Create event
|
||||
CLLocation *location = [locations lastObject];
|
||||
_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),
|
||||
},
|
||||
@"timestamp": @(CFAbsoluteTimeGetCurrent() * 1000.0) // in ms
|
||||
};
|
||||
|
||||
// Send event
|
||||
if (_observingLocation) {
|
||||
[_bridge.eventDispatcher sendDeviceEventWithName:@"geolocationDidChange"
|
||||
body:_lastLocationEvent];
|
||||
}
|
||||
|
||||
// Fire all queued callbacks
|
||||
for (RCTLocationRequest *request in _pendingRequests) {
|
||||
request.successBlock(@[_lastLocationEvent]);
|
||||
}
|
||||
[_pendingRequests removeAllObjects];
|
||||
|
||||
// Stop updating if not not observing
|
||||
if (!_observingLocation) {
|
||||
[_locationManager stopUpdatingLocation];
|
||||
}
|
||||
|
||||
// Reset location accuracy
|
||||
_locationManager.desiredAccuracy = RCT_DEFAULT_LOCATION_ACCURACY;
|
||||
}
|
||||
|
||||
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
|
||||
{
|
||||
// Check error type
|
||||
NSDictionary *jsError = nil;
|
||||
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) {
|
||||
[_bridge.eventDispatcher sendDeviceEventWithName:@"geolocationError"
|
||||
body:jsError];
|
||||
}
|
||||
|
||||
// Fire all queued error callbacks
|
||||
for (RCTLocationRequest *request in _pendingRequests) {
|
||||
request.errorBlock(@[jsError]);
|
||||
}
|
||||
[_pendingRequests removeAllObjects];
|
||||
|
||||
// Reset location accuracy
|
||||
_locationManager.desiredAccuracy = RCT_DEFAULT_LOCATION_ACCURACY;
|
||||
}
|
||||
|
||||
@end
|
|
@ -140,7 +140,7 @@ function setupXHR() {
|
|||
|
||||
function setupGeolocation() {
|
||||
GLOBAL.navigator = GLOBAL.navigator || {};
|
||||
GLOBAL.navigator.geolocation = require('GeoLocation');
|
||||
GLOBAL.navigator.geolocation = require('Geolocation');
|
||||
}
|
||||
|
||||
setupDocumentShim();
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
+ (float)float:(id)json;
|
||||
+ (int)int:(id)json;
|
||||
|
||||
+ (NSArray *)NSArray:(id)json;
|
||||
+ (NSDictionary *)NSDictionary:(id)json;
|
||||
+ (NSString *)NSString:(id)json;
|
||||
+ (NSNumber *)NSNumber:(id)json;
|
||||
+ (NSInteger)NSInteger:(id)json;
|
||||
|
|
|
@ -99,6 +99,8 @@ RCT_CONVERTER(double, double, doubleValue)
|
|||
RCT_CONVERTER(float, float, floatValue)
|
||||
RCT_CONVERTER(int, int, intValue)
|
||||
|
||||
RCT_CONVERTER_CUSTOM(NSArray *, NSArray, [NSArray arrayWithArray:json])
|
||||
RCT_CONVERTER_CUSTOM(NSDictionary *, NSDictionary, [NSDictionary dictionaryWithDictionary:json])
|
||||
RCT_CONVERTER(NSString *, NSString, description)
|
||||
RCT_CONVERTER_CUSTOM(NSNumber *, NSNumber, @([json doubleValue]))
|
||||
RCT_CONVERTER(NSInteger, NSInteger, integerValue)
|
||||
|
|
|
@ -1,182 +0,0 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import "RCTLocationObserver.h"
|
||||
|
||||
#import <CoreLocation/CLLocationManager.h>
|
||||
#import <CoreLocation/CLLocationManagerDelegate.h>
|
||||
|
||||
#import "RCTAssert.h"
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTEventDispatcher.h"
|
||||
#import "RCTLog.h"
|
||||
|
||||
// TODO (#5906496): Shouldn't these be configurable?
|
||||
const CLLocationAccuracy RCTLocationAccuracy = 500.0; // meters
|
||||
|
||||
@interface RCTPendingLocationRequest : NSObject
|
||||
|
||||
@property (nonatomic, copy) RCTResponseSenderBlock successBlock;
|
||||
@property (nonatomic, copy) RCTResponseSenderBlock errorBlock;
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTPendingLocationRequest @end
|
||||
|
||||
@interface RCTLocationObserver () <CLLocationManagerDelegate>
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTLocationObserver
|
||||
{
|
||||
CLLocationManager *_locationManager;
|
||||
NSDictionary *_lastLocationEvent;
|
||||
NSMutableDictionary *_pendingRequests;
|
||||
}
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_pendingRequests = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[_locationManager stopUpdatingLocation];
|
||||
}
|
||||
|
||||
#pragma mark - Public API
|
||||
|
||||
- (void)startObserving
|
||||
{
|
||||
RCT_EXPORT();
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
|
||||
// Create the location manager if this object does not
|
||||
// already have one, and it must be created and accessed
|
||||
// on the main thread
|
||||
if (nil == _locationManager) {
|
||||
_locationManager = [[CLLocationManager alloc] init];
|
||||
}
|
||||
|
||||
_locationManager.delegate = self;
|
||||
_locationManager.desiredAccuracy = RCTLocationAccuracy;
|
||||
|
||||
// Set a movement threshold for new events.
|
||||
_locationManager.distanceFilter = RCTLocationAccuracy; // meters
|
||||
|
||||
if([_locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) {
|
||||
[_locationManager requestWhenInUseAuthorization];
|
||||
}
|
||||
|
||||
[_locationManager startUpdatingLocation];
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
- (void)stopObserving
|
||||
{
|
||||
RCT_EXPORT();
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[_locationManager stopUpdatingLocation];
|
||||
_lastLocationEvent = nil;
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - CLLocationManagerDelegate
|
||||
|
||||
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
|
||||
{
|
||||
CLLocation *loc = [locations lastObject];
|
||||
NSDictionary *event = @{
|
||||
@"coords": @{
|
||||
@"latitude": @(loc.coordinate.latitude),
|
||||
@"longitude": @(loc.coordinate.longitude),
|
||||
@"altitude": @(loc.altitude),
|
||||
@"accuracy": @(RCTLocationAccuracy),
|
||||
@"altitudeAccuracy": @(RCTLocationAccuracy),
|
||||
@"heading": @(loc.course),
|
||||
@"speed": @(loc.speed),
|
||||
},
|
||||
@"timestamp": @(CACurrentMediaTime())
|
||||
};
|
||||
[_bridge.eventDispatcher sendDeviceEventWithName:@"geoLocationDidChange" body:event];
|
||||
NSArray *pendingRequestsCopy;
|
||||
|
||||
// TODO (#5906496): is this locking neccessary? If so, use something better than @synchronize
|
||||
@synchronized(self) {
|
||||
|
||||
pendingRequestsCopy = [_pendingRequests allValues];
|
||||
[_pendingRequests removeAllObjects];
|
||||
|
||||
_lastLocationEvent = event;
|
||||
}
|
||||
|
||||
for (RCTPendingLocationRequest *request in pendingRequestsCopy) {
|
||||
if (request.successBlock) {
|
||||
request.successBlock(@[event]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
|
||||
{
|
||||
NSArray *pendingRequestsCopy;
|
||||
|
||||
// TODO (#5906496): is this locking neccessary? If so, use something better than @synchronize
|
||||
@synchronized(self) {
|
||||
pendingRequestsCopy = [_pendingRequests allValues];
|
||||
[_pendingRequests removeAllObjects];
|
||||
}
|
||||
|
||||
NSString *errorMsg = @"User denied location service or location service not available.";
|
||||
for (RCTPendingLocationRequest *request in pendingRequestsCopy) {
|
||||
if (request.errorBlock) {
|
||||
request.errorBlock(@[errorMsg]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)getCurrentPosition:(RCTResponseSenderBlock)geoSuccess withErrorCallback:(RCTResponseSenderBlock)geoError
|
||||
{
|
||||
RCT_EXPORT();
|
||||
|
||||
NSDictionary *lastLocationCopy;
|
||||
// TODO (#5906496): is this locking neccessary? If so, use something better than @synchronize
|
||||
@synchronized(self) {
|
||||
if (![CLLocationManager locationServicesEnabled] || [CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied) {
|
||||
if (geoError) {
|
||||
NSString *errorMsg = @"User denied location service or location service not available.";
|
||||
geoError(@[errorMsg]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If a request for the current position comes in before the OS has informed us, we wait for the first
|
||||
// OS event and then call our callbacks. This obviates the need for handling of the otherwise
|
||||
// common failure case of requesting the geolocation until it succeeds, assuming we would have
|
||||
// instead returned an error if it wasn't yet available.
|
||||
if (!_lastLocationEvent) {
|
||||
NSInteger requestID = [_pendingRequests count];
|
||||
RCTPendingLocationRequest *request = [[RCTPendingLocationRequest alloc] init];
|
||||
request.successBlock = geoSuccess;
|
||||
request.errorBlock = geoError;
|
||||
_pendingRequests[@(requestID)] = request;
|
||||
return;
|
||||
} else {
|
||||
lastLocationCopy = [_lastLocationEvent copy];
|
||||
}
|
||||
}
|
||||
if (geoSuccess) {
|
||||
geoSuccess(@[lastLocationCopy]);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
|
@ -74,8 +74,9 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t
|
|||
_property = [RCTConvert NSString:config[@"property"]];
|
||||
|
||||
// TODO: this should be provided in ms, not seconds
|
||||
_duration = [RCTConvert NSTimeInterval:config[@"duration"]] ?: duration;
|
||||
_delay = [RCTConvert NSTimeInterval:config[@"delay"]];
|
||||
// (this will require changing all call sites to ms as well)
|
||||
_duration = [RCTConvert NSTimeInterval:config[@"duration"]] * 1000.0 ?: duration;
|
||||
_delay = [RCTConvert NSTimeInterval:config[@"delay"]] * 1000.0;
|
||||
_animationType = [RCTConvert RCTAnimationType:config[@"type"]];
|
||||
if (_animationType == RCTAnimationTypeSpring) {
|
||||
_springDamping = [RCTConvert CGFloat:config[@"springDamping"]];
|
||||
|
@ -135,7 +136,8 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t
|
|||
if ((self = [super init])) {
|
||||
|
||||
// TODO: this should be provided in ms, not seconds
|
||||
NSTimeInterval duration = [RCTConvert NSTimeInterval:config[@"duration"]];
|
||||
// (this will require changing all call sites to ms as well)
|
||||
NSTimeInterval duration = [RCTConvert NSTimeInterval:config[@"duration"]] * 1000.0;
|
||||
|
||||
_createAnimation = [[RCTAnimation alloc] initWithDuration:duration dictionary:config[@"create"]];
|
||||
_updateAnimation = [[RCTAnimation alloc] initWithDuration:duration dictionary:config[@"update"]];
|
||||
|
|
|
@ -35,7 +35,6 @@
|
|||
13E067571A70F44B002CDEE1 /* RCTView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067501A70F44B002CDEE1 /* RCTView.m */; };
|
||||
13E067591A70F44B002CDEE1 /* UIView+ReactKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067541A70F44B002CDEE1 /* UIView+ReactKit.m */; };
|
||||
58C571C11AA56C1900CDF9C8 /* RCTDatePickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58C571BF1AA56C1900CDF9C8 /* RCTDatePickerManager.m */; };
|
||||
5F5F0D991A9E456B001279FA /* RCTLocationObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F5F0D981A9E456B001279FA /* RCTLocationObserver.m */; };
|
||||
830A229E1A66C68A008503DA /* RCTRootView.m in Sources */ = {isa = PBXBuildFile; fileRef = 830A229D1A66C68A008503DA /* RCTRootView.m */; };
|
||||
830BA4551A8E3BDA00D53203 /* RCTCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 830BA4541A8E3BDA00D53203 /* RCTCache.m */; };
|
||||
832348161A77A5AA00B55238 /* Layout.c in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FC71A68125100A75B9A /* Layout.c */; };
|
||||
|
@ -126,8 +125,6 @@
|
|||
13EFFCCF1A98E6FE002607DC /* RCTJSMethodRegistrar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTJSMethodRegistrar.h; sourceTree = "<group>"; };
|
||||
58C571BF1AA56C1900CDF9C8 /* RCTDatePickerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDatePickerManager.m; sourceTree = "<group>"; };
|
||||
58C571C01AA56C1900CDF9C8 /* RCTDatePickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDatePickerManager.h; sourceTree = "<group>"; };
|
||||
5F5F0D971A9E456B001279FA /* RCTLocationObserver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTLocationObserver.h; sourceTree = "<group>"; };
|
||||
5F5F0D981A9E456B001279FA /* RCTLocationObserver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTLocationObserver.m; sourceTree = "<group>"; };
|
||||
830213F31A654E0800B993E6 /* RCTBridgeModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTBridgeModule.h; sourceTree = "<group>"; };
|
||||
830A229C1A66C68A008503DA /* RCTRootView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRootView.h; sourceTree = "<group>"; };
|
||||
830A229D1A66C68A008503DA /* RCTRootView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRootView.m; sourceTree = "<group>"; };
|
||||
|
@ -190,8 +187,6 @@
|
|||
13B07FE01A69315300A75B9A /* Modules */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5F5F0D971A9E456B001279FA /* RCTLocationObserver.h */,
|
||||
5F5F0D981A9E456B001279FA /* RCTLocationObserver.m */,
|
||||
13B07FE71A69327A00A75B9A /* RCTAlertManager.h */,
|
||||
13B07FE81A69327A00A75B9A /* RCTAlertManager.m */,
|
||||
13B07FE91A69327A00A75B9A /* RCTExceptionsManager.h */,
|
||||
|
@ -398,7 +393,6 @@
|
|||
13B0801E1A69489C00A75B9A /* RCTTextField.m in Sources */,
|
||||
13B07FEF1A69327A00A75B9A /* RCTAlertManager.m in Sources */,
|
||||
83CBBACC1A6023D300E9B192 /* RCTConvert.m in Sources */,
|
||||
5F5F0D991A9E456B001279FA /* RCTLocationObserver.m in Sources */,
|
||||
830A229E1A66C68A008503DA /* RCTRootView.m in Sources */,
|
||||
13B07FF01A69327A00A75B9A /* RCTExceptionsManager.m in Sources */,
|
||||
83CBBA5A1A601E9000E9B192 /* RCTRedBox.m in Sources */,
|
||||
|
|
Loading…
Reference in New Issue