From 705a8e0144775ff695f26a1f6b41d0baa6bed683 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Mon, 9 Mar 2015 03:04:44 -0700 Subject: [PATCH] Improved Geolocation API --- Examples/UIExplorer/GeoLocationExample.js | 10 +- .../UIExplorer.xcodeproj/project.pbxproj | 30 ++ Examples/UIExplorer/UIExplorerList.js | 2 +- .../{GeoLocation.js => Geolocation.ios.js} | 68 ++-- .../GeoLocation}/RCTLocationObserver.h | 0 Libraries/GeoLocation/RCTLocationObserver.m | 319 ++++++++++++++++++ .../InitializeJavaScriptAppEngine.js | 2 +- ReactKit/Base/RCTConvert.h | 2 + ReactKit/Base/RCTConvert.m | 2 + ReactKit/Modules/RCTLocationObserver.m | 182 ---------- ReactKit/Modules/RCTUIManager.m | 8 +- ReactKit/ReactKit.xcodeproj/project.pbxproj | 6 - 12 files changed, 399 insertions(+), 232 deletions(-) rename Libraries/GeoLocation/{GeoLocation.js => Geolocation.ios.js} (65%) rename {ReactKit/Modules => Libraries/GeoLocation}/RCTLocationObserver.h (100%) create mode 100644 Libraries/GeoLocation/RCTLocationObserver.m delete mode 100644 ReactKit/Modules/RCTLocationObserver.m diff --git a/Examples/UIExplorer/GeoLocationExample.js b/Examples/UIExplorer/GeoLocationExample.js index 1ab5f290c1f1ae..fac3dd205f3a13 100644 --- a/Examples/UIExplorer/GeoLocationExample.js +++ b/Examples/UIExplorer/GeoLocationExample.js @@ -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 ; + return ; }, } ]; -var GeoLocationExample = React.createClass({ +var GeolocationExample = React.createClass({ getInitialState: function() { return { initialPosition: 'unknown', diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj index 942ec24b09a8a5..ea13d28e3acd43 100644 --- a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj +++ b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj @@ -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 = ""; }; 13417FFA1AA91531003F314A /* ReactKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReactKit.xcodeproj; path = ../../ReactKit/ReactKit.xcodeproj; sourceTree = ""; }; 134180261AA91779003F314A /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = ../../Libraries/Network/RCTNetwork.xcodeproj; sourceTree = ""; }; + 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = ../../Libraries/Geolocation/RCTGeolocation.xcodeproj; sourceTree = ""; }; 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 = ""; }; 13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; @@ -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 = ""; }; + 134A8A211AACED6A00945AAE /* Products */ = { + isa = PBXGroup; + children = ( + 134A8A251AACED6A00945AAE /* libRCTGeolocation.a */, + ); + name = Products; + sourceTree = ""; + }; 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 */ diff --git a/Examples/UIExplorer/UIExplorerList.js b/Examples/UIExplorer/UIExplorerList.js index ee4130e4ab4e69..dd658b6adb36ac 100644 --- a/Examples/UIExplorer/UIExplorerList.js +++ b/Examples/UIExplorer/UIExplorerList.js @@ -32,7 +32,7 @@ var EXAMPLES = [ require('./ActivityIndicatorExample'), require('./ScrollViewExample'), require('./DatePickerExample'), - require('./GeoLocationExample'), + require('./GeolocationExample'), require('./TabBarExample'), ]; diff --git a/Libraries/GeoLocation/GeoLocation.js b/Libraries/GeoLocation/Geolocation.ios.js similarity index 65% rename from Libraries/GeoLocation/GeoLocation.js rename to Libraries/GeoLocation/Geolocation.ios.js index 9a7f792c462fa2..1b57192120f4dd 100644 --- a/Libraries/GeoLocation/GeoLocation.js +++ b/Libraries/GeoLocation/Geolocation.ios.js @@ -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; diff --git a/ReactKit/Modules/RCTLocationObserver.h b/Libraries/GeoLocation/RCTLocationObserver.h similarity index 100% rename from ReactKit/Modules/RCTLocationObserver.h rename to Libraries/GeoLocation/RCTLocationObserver.h diff --git a/Libraries/GeoLocation/RCTLocationObserver.m b/Libraries/GeoLocation/RCTLocationObserver.m new file mode 100644 index 00000000000000..0196536406f850 --- /dev/null +++ b/Libraries/GeoLocation/RCTLocationObserver.m @@ -0,0 +1,319 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import "RCTLocationObserver.h" + +#import +#import +#import + +#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 () + +@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 diff --git a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js index 3c507129b53b55..422447d4693e59 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js +++ b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js @@ -140,7 +140,7 @@ function setupXHR() { function setupGeolocation() { GLOBAL.navigator = GLOBAL.navigator || {}; - GLOBAL.navigator.geolocation = require('GeoLocation'); + GLOBAL.navigator.geolocation = require('Geolocation'); } setupDocumentShim(); diff --git a/ReactKit/Base/RCTConvert.h b/ReactKit/Base/RCTConvert.h index a5aa59fe60a66c..16cd6967ba707d 100644 --- a/ReactKit/Base/RCTConvert.h +++ b/ReactKit/Base/RCTConvert.h @@ -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; diff --git a/ReactKit/Base/RCTConvert.m b/ReactKit/Base/RCTConvert.m index bec60ff6c1eb6d..760f8284dfad3d 100644 --- a/ReactKit/Base/RCTConvert.m +++ b/ReactKit/Base/RCTConvert.m @@ -99,6 +99,8 @@ @implementation RCTConvert 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) diff --git a/ReactKit/Modules/RCTLocationObserver.m b/ReactKit/Modules/RCTLocationObserver.m deleted file mode 100644 index fd9c7ac077d3f9..00000000000000 --- a/ReactKit/Modules/RCTLocationObserver.m +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright 2004-present Facebook. All Rights Reserved. - -#import "RCTLocationObserver.h" - -#import -#import - -#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 () - -@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 diff --git a/ReactKit/Modules/RCTUIManager.m b/ReactKit/Modules/RCTUIManager.m index 47b2e6c844e9d4..a22f2454051b55 100644 --- a/ReactKit/Modules/RCTUIManager.m +++ b/ReactKit/Modules/RCTUIManager.m @@ -74,8 +74,9 @@ - (instancetype)initWithDuration:(NSTimeInterval)duration dictionary:(NSDictiona _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 @@ - (instancetype)initWithDictionary:(NSDictionary *)config callback:(RCTResponseS 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"]]; diff --git a/ReactKit/ReactKit.xcodeproj/project.pbxproj b/ReactKit/ReactKit.xcodeproj/project.pbxproj index cb3cb840c3384e..63dff04fb743b7 100644 --- a/ReactKit/ReactKit.xcodeproj/project.pbxproj +++ b/ReactKit/ReactKit.xcodeproj/project.pbxproj @@ -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 = ""; }; 58C571BF1AA56C1900CDF9C8 /* RCTDatePickerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDatePickerManager.m; sourceTree = ""; }; 58C571C01AA56C1900CDF9C8 /* RCTDatePickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDatePickerManager.h; sourceTree = ""; }; - 5F5F0D971A9E456B001279FA /* RCTLocationObserver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTLocationObserver.h; sourceTree = ""; }; - 5F5F0D981A9E456B001279FA /* RCTLocationObserver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTLocationObserver.m; sourceTree = ""; }; 830213F31A654E0800B993E6 /* RCTBridgeModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTBridgeModule.h; sourceTree = ""; }; 830A229C1A66C68A008503DA /* RCTRootView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRootView.h; sourceTree = ""; }; 830A229D1A66C68A008503DA /* RCTRootView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRootView.m; sourceTree = ""; }; @@ -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 */,