mirror of
https://github.com/status-im/react-native.git
synced 2025-02-24 15:18:10 +00:00
Merge branch 'oss-sync/master' into import_everycommit
This commit is contained in:
commit
970dd8acca
@ -24,33 +24,44 @@ var {
|
||||
View,
|
||||
} = React;
|
||||
|
||||
var regionText = {
|
||||
latitude: '0',
|
||||
longitude: '0',
|
||||
latitudeDelta: '0',
|
||||
longitudeDelta: '0',
|
||||
}
|
||||
|
||||
var MapRegionInput = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
region: React.PropTypes.shape({
|
||||
latitude: React.PropTypes.number,
|
||||
longitude: React.PropTypes.number,
|
||||
latitudeDelta: React.PropTypes.number,
|
||||
longitudeDelta: React.PropTypes.number,
|
||||
latitude: React.PropTypes.number.isRequired,
|
||||
longitude: React.PropTypes.number.isRequired,
|
||||
latitudeDelta: React.PropTypes.number.isRequired,
|
||||
longitudeDelta: React.PropTypes.number.isRequired,
|
||||
}),
|
||||
onChange: React.PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
region: {
|
||||
latitude: 0,
|
||||
longitude: 0,
|
||||
latitudeDelta: 0,
|
||||
longitudeDelta: 0,
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(nextProps) {
|
||||
this.setState(nextProps.region);
|
||||
this.setState({
|
||||
region: nextProps.region || this.getInitialState().region
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var region = this.state;
|
||||
var region = this.state.region || this.getInitialState().region;
|
||||
return (
|
||||
<View>
|
||||
<View style={styles.row}>
|
||||
@ -61,6 +72,7 @@ var MapRegionInput = React.createClass({
|
||||
value={'' + region.latitude}
|
||||
style={styles.textInput}
|
||||
onChange={this._onChangeLatitude}
|
||||
selectTextOnFocus={true}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.row}>
|
||||
@ -71,6 +83,7 @@ var MapRegionInput = React.createClass({
|
||||
value={'' + region.longitude}
|
||||
style={styles.textInput}
|
||||
onChange={this._onChangeLongitude}
|
||||
selectTextOnFocus={true}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.row}>
|
||||
@ -81,6 +94,7 @@ var MapRegionInput = React.createClass({
|
||||
value={'' + region.latitudeDelta}
|
||||
style={styles.textInput}
|
||||
onChange={this._onChangeLatitudeDelta}
|
||||
selectTextOnFocus={true}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.row}>
|
||||
@ -91,6 +105,7 @@ var MapRegionInput = React.createClass({
|
||||
value={'' + region.longitudeDelta}
|
||||
style={styles.textInput}
|
||||
onChange={this._onChangeLongitudeDelta}
|
||||
selectTextOnFocus={true}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.changeButton}>
|
||||
@ -103,23 +118,29 @@ var MapRegionInput = React.createClass({
|
||||
},
|
||||
|
||||
_onChangeLatitude: function(e) {
|
||||
this.setState({latitude: parseFloat(e.nativeEvent.text)});
|
||||
regionText.latitude = e.nativeEvent.text;
|
||||
},
|
||||
|
||||
_onChangeLongitude: function(e) {
|
||||
this.setState({longitude: parseFloat(e.nativeEvent.text)});
|
||||
regionText.longitude = e.nativeEvent.text;
|
||||
},
|
||||
|
||||
_onChangeLatitudeDelta: function(e) {
|
||||
this.setState({latitudeDelta: parseFloat(e.nativeEvent.text)});
|
||||
regionText.latitudeDelta = e.nativeEvent.text;
|
||||
},
|
||||
|
||||
_onChangeLongitudeDelta: function(e) {
|
||||
this.setState({longitudeDelta: parseFloat(e.nativeEvent.text)});
|
||||
regionText.longitudeDelta = e.nativeEvent.text;
|
||||
},
|
||||
|
||||
_change: function() {
|
||||
this.props.onChange(this.state);
|
||||
this.setState({
|
||||
latitude: parseFloat(regionText.latitude),
|
||||
longitude: parseFloat(regionText.longitude),
|
||||
latitudeDelta: parseFloat(regionText.latitudeDelta),
|
||||
longitudeDelta: parseFloat(regionText.longitudeDelta),
|
||||
});
|
||||
this.props.onChange(this.state.region);
|
||||
},
|
||||
|
||||
});
|
||||
@ -130,6 +151,8 @@ var MapViewExample = React.createClass({
|
||||
return {
|
||||
mapRegion: null,
|
||||
mapRegionInput: null,
|
||||
annotations: null,
|
||||
isFirstLoad: true,
|
||||
};
|
||||
},
|
||||
|
||||
@ -138,8 +161,10 @@ var MapViewExample = React.createClass({
|
||||
<View>
|
||||
<MapView
|
||||
style={styles.map}
|
||||
onRegionChange={this._onRegionChanged}
|
||||
onRegionChange={this._onRegionChange}
|
||||
onRegionChangeComplete={this._onRegionChangeComplete}
|
||||
region={this.state.mapRegion}
|
||||
annotations={this.state.annotations}
|
||||
/>
|
||||
<MapRegionInput
|
||||
onChange={this._onRegionInputChanged}
|
||||
@ -149,14 +174,35 @@ var MapViewExample = React.createClass({
|
||||
);
|
||||
},
|
||||
|
||||
_onRegionChanged(region) {
|
||||
this.setState({mapRegionInput: region});
|
||||
_getAnnotations(region) {
|
||||
return [{
|
||||
longitude: region.longitude,
|
||||
latitude: region.latitude,
|
||||
title: 'You Are Here',
|
||||
}];
|
||||
},
|
||||
|
||||
_onRegionChange(region) {
|
||||
this.setState({
|
||||
mapRegionInput: region,
|
||||
});
|
||||
},
|
||||
|
||||
_onRegionChangeComplete(region) {
|
||||
if (this.state.isFirstLoad) {
|
||||
this.setState({
|
||||
mapRegionInput: region,
|
||||
annotations: this._getAnnotations(region),
|
||||
isFirstLoad: false,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_onRegionInputChanged(region) {
|
||||
this.setState({
|
||||
mapRegion: region,
|
||||
mapRegionInput: region,
|
||||
annotations: this._getAnnotations(region),
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -109,7 +109,7 @@ var styles = StyleSheet.create({
|
||||
flex: 1,
|
||||
},
|
||||
label: {
|
||||
width: 80,
|
||||
width: 120,
|
||||
justifyContent: 'flex-end',
|
||||
flexDirection: 'row',
|
||||
marginRight: 10,
|
||||
@ -311,4 +311,29 @@ exports.examples = [
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Clear and select',
|
||||
render: function () {
|
||||
return (
|
||||
<View>
|
||||
<WithLabel label="clearTextOnFocus">
|
||||
<TextInput
|
||||
placeholder="text is cleared on focus"
|
||||
value="text is cleared on focus"
|
||||
style={styles.default}
|
||||
clearTextOnFocus={true}
|
||||
/>
|
||||
</WithLabel>
|
||||
<WithLabel label="selectTextOnFocus">
|
||||
<TextInput
|
||||
placeholder="text is selected on focus"
|
||||
value="text is selected on focus"
|
||||
style={styles.default}
|
||||
selectTextOnFocus={true}
|
||||
/>
|
||||
</WithLabel>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
},
|
||||
];
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 28 KiB |
@ -39,9 +39,10 @@
|
||||
#endif
|
||||
NSString *version = [[UIDevice currentDevice] systemVersion];
|
||||
RCTAssert([version isEqualToString:@"8.1"], @"Snapshot tests should be run on iOS 8.1, found %@", version);
|
||||
_runner = initRunnerForApp(@"Examples/UIExplorer/UIExplorerApp");
|
||||
_runner = RCTInitRunnerForApp(@"Examples/UIExplorer/UIExplorerApp");
|
||||
|
||||
// If tests have changes, set recordMode = YES below and run the affected tests on an iPhone5, iOS 8.1 simulator.
|
||||
// If tests have changes, set recordMode = YES below and run the affected
|
||||
// tests on an iPhone5, iOS 8.1 simulator.
|
||||
_runner.recordMode = NO;
|
||||
}
|
||||
|
||||
@ -58,8 +59,10 @@
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Make sure this test runs first (underscores sort early) otherwise the other tests will tear out the rootView
|
||||
- (void)test__RootViewLoadsAndRenders {
|
||||
// Make sure this test runs first (underscores sort early) otherwise the
|
||||
// other tests will tear out the rootView
|
||||
- (void)test__RootViewLoadsAndRenders
|
||||
{
|
||||
UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
|
||||
RCTAssert([vc.view isKindOfClass:[RCTRootView class]], @"This test must run first.");
|
||||
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
|
||||
|
@ -18,7 +18,8 @@
|
||||
|
||||
@end
|
||||
|
||||
@implementation IntegrationTestsTests {
|
||||
@implementation IntegrationTestsTests
|
||||
{
|
||||
RCTTestRunner *_runner;
|
||||
}
|
||||
|
||||
@ -28,10 +29,11 @@
|
||||
RCTAssert(!__LP64__, @"Tests should be run on 32-bit device simulators (e.g. iPhone 5)");
|
||||
#endif
|
||||
NSString *version = [[UIDevice currentDevice] systemVersion];
|
||||
RCTAssert([version isEqualToString:@"8.1"], @"Tests should be run on iOS 8.1, found %@", version);
|
||||
_runner = initRunnerForApp(@"IntegrationTests/IntegrationTestsApp");
|
||||
RCTAssert([version integerValue] == 8, @"Tests should be run on iOS 8.x, found %@", version);
|
||||
_runner = RCTInitRunnerForApp(@"IntegrationTests/IntegrationTestsApp");
|
||||
|
||||
// If tests have changes, set recordMode = YES below and run the affected tests on an iPhone5, iOS 8.1 simulator.
|
||||
// If tests have changes, set recordMode = YES below and run the affected
|
||||
// tests on an iPhone5, iOS 8.1 simulator.
|
||||
_runner.recordMode = NO;
|
||||
}
|
||||
|
||||
@ -44,15 +46,19 @@
|
||||
|
||||
- (void)testTheTester_waitOneFrame
|
||||
{
|
||||
[_runner runTest:_cmd module:@"IntegrationTestHarnessTest" initialProps:@{@"waitOneFrame": @YES} expectErrorBlock:nil];
|
||||
[_runner runTest:_cmd
|
||||
module:@"IntegrationTestHarnessTest"
|
||||
initialProps:@{@"waitOneFrame": @YES}
|
||||
expectErrorBlock:nil];
|
||||
}
|
||||
|
||||
- (void)testTheTester_ExpectError
|
||||
// TODO: this seems to stall forever - figure out why
|
||||
- (void)DISABLED_testTheTester_ExpectError
|
||||
{
|
||||
[_runner runTest:_cmd
|
||||
module:@"IntegrationTestHarnessTest"
|
||||
initialProps:@{@"shouldThrow": @YES}
|
||||
expectErrorRegex:[NSRegularExpression regularExpressionWithPattern:@"because shouldThrow" options:0 error:nil]];
|
||||
expectErrorRegex:@"because shouldThrow"];
|
||||
}
|
||||
|
||||
- (void)testTimers
|
||||
|
@ -95,6 +95,23 @@ var MapView = React.createClass({
|
||||
longitudeDelta: React.PropTypes.number.isRequired,
|
||||
}),
|
||||
|
||||
/**
|
||||
* Map annotations with title/subtitle.
|
||||
*/
|
||||
annotations: React.PropTypes.arrayOf(React.PropTypes.shape({
|
||||
/**
|
||||
* The location of the annotation.
|
||||
*/
|
||||
latitude: React.PropTypes.number.isRequired,
|
||||
longitude: React.PropTypes.number.isRequired,
|
||||
|
||||
/**
|
||||
* Annotation title/subtile.
|
||||
*/
|
||||
title: React.PropTypes.string,
|
||||
subtitle: React.PropTypes.string,
|
||||
})),
|
||||
|
||||
/**
|
||||
* Maximum size of area that can be displayed.
|
||||
*/
|
||||
@ -142,6 +159,7 @@ var MapView = React.createClass({
|
||||
pitchEnabled={this.props.pitchEnabled}
|
||||
scrollEnabled={this.props.scrollEnabled}
|
||||
region={this.props.region}
|
||||
annotations={this.props.annotations}
|
||||
maxDelta={this.props.maxDelta}
|
||||
minDelta={this.props.minDelta}
|
||||
legalLabelInsets={this.props.legalLabelInsets}
|
||||
@ -165,6 +183,7 @@ var RCTMap = createReactIOSNativeComponentClass({
|
||||
pitchEnabled: true,
|
||||
scrollEnabled: true,
|
||||
region: {diff: deepDiffer},
|
||||
annotations: {diff: deepDiffer},
|
||||
maxDelta: true,
|
||||
minDelta: true,
|
||||
legalLabelInsets: {diff: insetsDiffer},
|
||||
|
@ -58,6 +58,8 @@ var RCTTextFieldAttributes = merge(RCTTextViewAttributes, {
|
||||
caretHidden: true,
|
||||
enabled: true,
|
||||
clearButtonMode: true,
|
||||
clearTextOnFocus: true,
|
||||
selectTextOnFocus: true,
|
||||
});
|
||||
|
||||
var onlyMultiline = {
|
||||
@ -267,7 +269,17 @@ var TextInput = React.createClass({
|
||||
'unless-editing',
|
||||
'always',
|
||||
]),
|
||||
|
||||
/**
|
||||
* If true, clears the text field automatically when editing begins
|
||||
*/
|
||||
clearTextOnFocus: PropTypes.bool,
|
||||
/**
|
||||
* If true, selected the text automatically when editing begins
|
||||
*/
|
||||
selectTextOnFocus: PropTypes.bool,
|
||||
/**
|
||||
* Styles
|
||||
*/
|
||||
style: Text.propTypes.style,
|
||||
/**
|
||||
* Used to locate this view in end-to-end tests.
|
||||
@ -431,6 +443,8 @@ var TextInput = React.createClass({
|
||||
autoCapitalize={autoCapitalize}
|
||||
autoCorrect={this.props.autoCorrect}
|
||||
clearButtonMode={clearButtonMode}
|
||||
clearTextOnFocus={this.props.clearTextOnFocus}
|
||||
selectTextOnFocus={this.props.selectTextOnFocus}
|
||||
/>;
|
||||
} else {
|
||||
for (var propKey in notMultiline) {
|
||||
|
@ -19,6 +19,19 @@
|
||||
#import "RCTImageDownloader.h"
|
||||
#import "RCTLog.h"
|
||||
|
||||
static dispatch_queue_t RCTImageLoaderQueue(void)
|
||||
{
|
||||
static dispatch_queue_t queue = NULL;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
queue = dispatch_queue_create("com.facebook.rctImageLoader", DISPATCH_QUEUE_SERIAL);
|
||||
dispatch_set_target_queue(queue,
|
||||
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
|
||||
});
|
||||
|
||||
return queue;
|
||||
}
|
||||
|
||||
NSError *errorWithMessage(NSString *message)
|
||||
{
|
||||
NSDictionary *errorInfo = @{NSLocalizedDescriptionKey: message};
|
||||
@ -43,10 +56,20 @@ NSError *errorWithMessage(NSString *message)
|
||||
if ([imageTag hasPrefix:@"assets-library"]) {
|
||||
[[RCTImageLoader assetsLibrary] assetForURL:[NSURL URLWithString:imageTag] resultBlock:^(ALAsset *asset) {
|
||||
if (asset) {
|
||||
// ALAssetLibrary API is async and will be multi-threaded. Loading a few full
|
||||
// resolution images at once will spike the memory up to store the image data,
|
||||
// and might trigger memory warnings and/or OOM crashes.
|
||||
// To improve this, process the loaded asset in a serial queue.
|
||||
dispatch_async(RCTImageLoaderQueue(), ^{
|
||||
// Also make sure the image is released immediately after it's used so it
|
||||
// doesn't spike the memory up during the process.
|
||||
@autoreleasepool {
|
||||
ALAssetRepresentation *representation = [asset defaultRepresentation];
|
||||
ALAssetOrientation orientation = [representation orientation];
|
||||
UIImage *image = [UIImage imageWithCGImage:[representation fullResolutionImage] scale:1.0f orientation:(UIImageOrientation)orientation];
|
||||
callback(nil, image);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@ with no error message.", imageTag];
|
||||
NSError *error = errorWithMessage(errorText);
|
||||
|
@ -7,7 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule loadSourceMap
|
||||
* @flow
|
||||
* -- disabled flow due to mysterious validation errors --
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
37
Libraries/Promise.js
Normal file
37
Libraries/Promise.js
Normal file
@ -0,0 +1,37 @@
|
||||
/**
|
||||
*
|
||||
* Copyright 2013-2014 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @providesModule Promise
|
||||
*
|
||||
* This module wraps and augments the minimally ES6-compliant Promise
|
||||
* implementation provided by the promise npm package.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
global.setImmediate = require('setImmediate');
|
||||
var Promise = require('promise/setimmediate/es6-extensions');
|
||||
require('promise/setimmediate/done');
|
||||
|
||||
/**
|
||||
* Handle either fulfillment or rejection with the same callback.
|
||||
*/
|
||||
Promise.prototype.finally = function(onSettled) {
|
||||
return this.then(onSettled, onSettled);
|
||||
};
|
||||
|
||||
|
||||
module.exports = Promise;
|
@ -10,13 +10,13 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
* Use the initRunnerForApp macro for typical usage.
|
||||
* Use the RCTInitRunnerForApp macro for typical usage.
|
||||
*
|
||||
* Add this to your test target's gcc preprocessor macros:
|
||||
*
|
||||
* FB_REFERENCE_IMAGE_DIR="\"$(SOURCE_ROOT)/$(PROJECT_NAME)Tests/ReferenceImages\""
|
||||
*/
|
||||
#define initRunnerForApp(app__) [[RCTTestRunner alloc] initWithApp:(app__) referenceDir:@FB_REFERENCE_IMAGE_DIR]
|
||||
#define RCTInitRunnerForApp(app__) [[RCTTestRunner alloc] initWithApp:(app__) referenceDir:@FB_REFERENCE_IMAGE_DIR]
|
||||
|
||||
@interface RCTTestRunner : NSObject
|
||||
|
||||
@ -24,22 +24,25 @@
|
||||
@property (nonatomic, strong) NSURL *scriptURL;
|
||||
|
||||
/**
|
||||
* Initialize a runner. It's recommended that you use the initRunnerForApp macro instead of calling this directly.
|
||||
* Initialize a runner. It's recommended that you use the RCTInitRunnerForApp
|
||||
* macro instead of calling this directly.
|
||||
*
|
||||
* @param app The path to the app bundle without suffixes, e.g. IntegrationTests/IntegrationTestsApp
|
||||
* @param referencesDir The path for snapshot references images. The initRunnerForApp macro uses
|
||||
* @param referencesDir The path for snapshot references images. The RCTInitRunnerForApp macro uses
|
||||
* FB_REFERENCE_IMAGE_DIR for this automatically.
|
||||
*/
|
||||
- (instancetype)initWithApp:(NSString *)app referenceDir:(NSString *)referenceDir;
|
||||
|
||||
/**
|
||||
* Simplest runTest function simply mounts the specified JS module with no initialProps and waits for it to call
|
||||
* Simplest runTest function simply mounts the specified JS module with no
|
||||
* initialProps and waits for it to call
|
||||
*
|
||||
* RCTTestModule.markTestCompleted()
|
||||
*
|
||||
* JS errors/exceptions and timeouts will fail the test. Snapshot tests call RCTTestModule.verifySnapshot whenever they
|
||||
* want to verify what has been rendered (typically via requestAnimationFrame to make sure the latest state has been
|
||||
* rendered in native.
|
||||
* JS errors/exceptions and timeouts will fail the test. Snapshot tests call
|
||||
* RCTTestModule.verifySnapshot whenever they want to verify what has been
|
||||
* rendered (typically via requestAnimationFrame to make sure the latest state
|
||||
* has been rendered in native.
|
||||
*
|
||||
* @param test Selector of the test, usually just `_cmd`.
|
||||
* @param moduleName Name of the JS component as registered by `AppRegistry.registerComponent` in JS.
|
||||
@ -47,8 +50,9 @@
|
||||
- (void)runTest:(SEL)test module:(NSString *)moduleName;
|
||||
|
||||
/**
|
||||
* Same as runTest:, but allows for passing initialProps for providing mock data or requesting different behaviors, and
|
||||
* expectErrorRegex verifies that the error you expected was thrown.
|
||||
* Same as runTest:, but allows for passing initialProps for providing mock data
|
||||
* or requesting different behaviors, and expectErrorRegex verifies that the
|
||||
* error you expected was thrown.
|
||||
*
|
||||
* @param test Selector of the test, usually just `_cmd`.
|
||||
* @param moduleName Name of the JS component as registered by `AppRegistry.registerComponent` in JS.
|
||||
@ -58,8 +62,9 @@
|
||||
- (void)runTest:(SEL)test module:(NSString *)moduleName initialProps:(NSDictionary *)initialProps expectErrorRegex:(NSString *)expectErrorRegex;
|
||||
|
||||
/**
|
||||
* Same as runTest:, but allows for passing initialProps for providing mock data or requesting different behaviors, and
|
||||
* expectErrorBlock provides arbitrary logic for processing errors (nil will cause any error to fail the test).
|
||||
* Same as runTest:, but allows for passing initialProps for providing mock data
|
||||
* or requesting different behaviors, and expectErrorBlock provides arbitrary
|
||||
* logic for processing errors (nil will cause any error to fail the test).
|
||||
*
|
||||
* @param test Selector of the test, usually just `_cmd`.
|
||||
* @param moduleName Name of the JS component as registered by `AppRegistry.registerComponent` in JS.
|
||||
|
@ -49,7 +49,8 @@
|
||||
[self runTest:test module:moduleName initialProps:nil expectErrorBlock:nil];
|
||||
}
|
||||
|
||||
- (void)runTest:(SEL)test module:(NSString *)moduleName initialProps:(NSDictionary *)initialProps expectErrorRegex:(NSString *)errorRegex
|
||||
- (void)runTest:(SEL)test module:(NSString *)moduleName
|
||||
initialProps:(NSDictionary *)initialProps expectErrorRegex:(NSString *)errorRegex
|
||||
{
|
||||
[self runTest:test module:moduleName initialProps:initialProps expectErrorBlock:^BOOL(NSString *error){
|
||||
return [error rangeOfString:errorRegex options:NSRegularExpressionSearch].location != NSNotFound;
|
||||
|
@ -114,7 +114,9 @@
|
||||
- (NSNumber *)reactTagAtPoint:(CGPoint)point
|
||||
{
|
||||
CGFloat fraction;
|
||||
NSUInteger characterIndex = [_layoutManager characterIndexForPoint:point inTextContainer:_textContainer fractionOfDistanceBetweenInsertionPoints:&fraction];
|
||||
NSUInteger characterIndex = [_layoutManager characterIndexForPoint:point
|
||||
inTextContainer:_textContainer
|
||||
fractionOfDistanceBetweenInsertionPoints:&fraction];
|
||||
|
||||
NSNumber *reactTag = nil;
|
||||
|
||||
|
364
Libraries/vendor/core/ES6Promise.js
vendored
364
Libraries/vendor/core/ES6Promise.js
vendored
@ -1,364 +0,0 @@
|
||||
/**
|
||||
* @generated SignedSource<<d169e3bbcd91c2e26877882e0d02f289>>
|
||||
*
|
||||
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
* !! This file is a check-in of a static_upstream project! !!
|
||||
* !! !!
|
||||
* !! You should not modify this file directly. Instead: !!
|
||||
* !! 1) Use `fjs use-upstream` to temporarily replace this with !!
|
||||
* !! the latest version from upstream. !!
|
||||
* !! 2) Make your changes, test them, etc. !!
|
||||
* !! 3) Use `fjs push-upstream` to copy your changes back to !!
|
||||
* !! static_upstream. !!
|
||||
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
*
|
||||
* Copyright 2013-2014 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @providesModule ES6Promise
|
||||
*
|
||||
* This module implements the minimum functionality necessary to comply
|
||||
* with chapter 25.4 of the ES6 specification. Any extensions to Promise
|
||||
* or Promise.prototype should be added in the Promise module.
|
||||
*
|
||||
* people.mozilla.org/~jorendorff/es6-draft.html#sec-promise-objects
|
||||
*/
|
||||
|
||||
module.exports = (function(global, undefined) {
|
||||
'use strict';
|
||||
|
||||
var setImmediate = require('setImmediate');
|
||||
|
||||
// These are the possible values for slots(promise).state.
|
||||
var PENDING_STATE = 'pending';
|
||||
var FULFILLED_STATE = 'fulfilled';
|
||||
var REJECTED_STATE = 'rejected';
|
||||
|
||||
// The ES6 specification makes heavy use of a notion of internal slots.
|
||||
// Some of these slots are best implemented as closure variables, such
|
||||
// as the alreadySettled variable in createResolvingFunctions, which
|
||||
// corresponds to the resolve.[[AlreadyResolved]].value property in the
|
||||
// specification. Other slots are best implemented as properties of a
|
||||
// slots object attached to the host object by a pseudo-private
|
||||
// property. The latter kind of slots may be accessed by passing the
|
||||
// host object (such as a Promise or a resolve/reject function object)
|
||||
// to the slots function; e.g., the slots(promise).state slot, which
|
||||
// corresponds to promise.[[PromiseState]] in the specification.
|
||||
var slotsKey = '__slots$' + Math.random().toString(36).slice(2);
|
||||
function slots(obj) {
|
||||
var result = obj[slotsKey];
|
||||
if (!result) {
|
||||
// In ES5+ environments, this property will be safely non-writable,
|
||||
// non-configurable, and non-enumerable. This implementation does
|
||||
// not logically rely on those niceties, however, so this code works
|
||||
// just fine in pre-ES5 environments, too.
|
||||
obj[slotsKey] = result = {};
|
||||
if (Object.defineProperty) try {
|
||||
Object.defineProperty(obj, slotsKey, { value: result });
|
||||
} catch (definePropertyIsBrokenInIE8) {}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Reusable callback functions. The identify function is the default
|
||||
// when onFulfilled is undefined or null, and the raise function is the
|
||||
// default when onRejected is undefined or null.
|
||||
function identity(x) { return x; }
|
||||
function raise(x) { throw x; }
|
||||
|
||||
/**
|
||||
* When the Promise function is called with argument executor, the
|
||||
* following steps are taken:
|
||||
* people.mozilla.org/~jorendorff/es6-draft.html#sec-promise
|
||||
*
|
||||
* The executor argument must be a function object. It is called for
|
||||
* initiating and reporting completion of the possibly deferred action
|
||||
* represented by this Promise object. The executor is called with two
|
||||
* arguments: resolve and reject. These are functions that may be used
|
||||
* by the executor function to report eventual completion or failure of
|
||||
* the deferred computation. Returning from the executor function does
|
||||
* not mean that the deferred action has been completed, but only that
|
||||
* the request to eventually perform the deferred action has been
|
||||
* accepted.
|
||||
*
|
||||
* The resolve function that is passed to an executor function accepts a
|
||||
* single argument. The executor code may eventually call the resolve
|
||||
* function to indicate that it wishes to resolve the associated Promise
|
||||
* object. The argument passed to the resolve function represents the
|
||||
* eventual value of the deferred action and can be either the actual
|
||||
* fulfillment value or another Promise object which will provide the
|
||||
* value if it is fullfilled.
|
||||
*
|
||||
* The reject function that is passed to an executor function accepts a
|
||||
* single argument. The executor code may eventually call the reject
|
||||
* function to indicate that the associated Promise is rejected and will
|
||||
* never be fulfilled. The argument passed to the reject function is
|
||||
* used as the rejection value of the promise. Typically it will be an
|
||||
* Error object.
|
||||
*
|
||||
* When Promise is called as a function rather than as a constructor, it
|
||||
* initializes its this value with the internal state necessary to
|
||||
* support the Promise.prototype methods.
|
||||
*
|
||||
* The Promise constructor is designed to be subclassable. It may be
|
||||
* used as the value in an extends clause of a class
|
||||
* definition. Subclass constructors that intend to inherit the
|
||||
* specified Promise behaviour must include a super call to Promise,
|
||||
* e.g. by invoking Promise.call(this, executor).
|
||||
*
|
||||
* people.mozilla.org/~jorendorff/es6-draft.html#sec-promise-constructor
|
||||
*/
|
||||
function Promise(executor) {
|
||||
var promiseSlots = slots(this);
|
||||
promiseSlots.state = PENDING_STATE;
|
||||
promiseSlots.fulfillReactions = [];
|
||||
promiseSlots.rejectReactions = [];
|
||||
|
||||
var resolvingFunctions = createResolvingFunctions(this);
|
||||
var reject = resolvingFunctions.reject;
|
||||
|
||||
try {
|
||||
executor(resolvingFunctions.resolve, reject);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
function createResolvingFunctions(promise) {
|
||||
var alreadySettled = false;
|
||||
|
||||
return {
|
||||
resolve: function(resolution) {
|
||||
if (!alreadySettled) {
|
||||
alreadySettled = true;
|
||||
|
||||
if (resolution === promise) {
|
||||
return settlePromise(
|
||||
promise,
|
||||
REJECTED_STATE,
|
||||
new TypeError('Cannot resolve promise with itself')
|
||||
);
|
||||
}
|
||||
|
||||
// To be treated as a Promise-like object, the resolution only
|
||||
// needs to be an object with a callable .then method.
|
||||
if (!resolution ||
|
||||
typeof resolution !== "object" ||
|
||||
typeof resolution.then !== "function") {
|
||||
return settlePromise(promise, FULFILLED_STATE, resolution);
|
||||
}
|
||||
|
||||
var resolvingFunctions = createResolvingFunctions(promise);
|
||||
var reject = resolvingFunctions.reject;
|
||||
|
||||
try {
|
||||
resolution.then(resolvingFunctions.resolve, reject);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
reject: function(reason) {
|
||||
if (!alreadySettled) {
|
||||
alreadySettled = true;
|
||||
settlePromise(promise, REJECTED_STATE, reason);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// This function unifies the FulfillPromise and RejectPromise functions
|
||||
// defined in the ES6 specification.
|
||||
function settlePromise(promise, state, result) {
|
||||
var promiseSlots = slots(promise);
|
||||
if (promiseSlots.state !== PENDING_STATE) {
|
||||
throw new Error('Settling a ' + promiseSlots.state + ' promise');
|
||||
}
|
||||
|
||||
var reactions;
|
||||
if (state === FULFILLED_STATE) {
|
||||
reactions = promiseSlots.fulfillReactions;
|
||||
} else if (state === REJECTED_STATE) {
|
||||
reactions = promiseSlots.rejectReactions;
|
||||
}
|
||||
|
||||
promiseSlots.result = result;
|
||||
promiseSlots.fulfillReactions = undefined;
|
||||
promiseSlots.rejectReactions = undefined;
|
||||
promiseSlots.state = state;
|
||||
|
||||
var count = reactions.length;
|
||||
count && setImmediate(function() {
|
||||
for (var i = 0; i < count; ++i) {
|
||||
reactions[i](promiseSlots.result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The Promise.all function returns a new promise which is fulfilled
|
||||
* with an array of fulfillment values for the passed promises, or
|
||||
* rejects with the reason of the first passed promise that rejects. It
|
||||
* resoves all elements of the passed iterable to promises as it runs
|
||||
* this algorithm.
|
||||
*
|
||||
* people.mozilla.org/~jorendorff/es6-draft.html#sec-promise.all
|
||||
*/
|
||||
Promise.all = function(array) {
|
||||
var Promise = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
var results = [];
|
||||
var remaining = 0;
|
||||
array.forEach(function(element, index) {
|
||||
++remaining; // Array might be sparse.
|
||||
Promise.resolve(element).then(function(result) {
|
||||
if (!results.hasOwnProperty(index)) {
|
||||
results[index] = result;
|
||||
--remaining || resolve(results);
|
||||
}
|
||||
}, reject);
|
||||
});
|
||||
remaining || resolve(results);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* The Promise.race function returns a new promise which is settled in
|
||||
* the same way as the first passed promise to settle. It resolves all
|
||||
* elements of the passed iterable to promises as it runs this
|
||||
* algorithm.
|
||||
*
|
||||
* people.mozilla.org/~jorendorff/es6-draft.html#sec-promise.race
|
||||
*/
|
||||
Promise.race = function(array) {
|
||||
var Promise = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
array.forEach(function(element) {
|
||||
Promise.resolve(element).then(resolve, reject);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* The Promise.resolve function returns either a new promise resolved
|
||||
* with the passed argument, or the argument itself if the argument a
|
||||
* promise produced by this construtor.
|
||||
*
|
||||
* people.mozilla.org/~jorendorff/es6-draft.html#sec-promise.resolve
|
||||
*/
|
||||
Promise.resolve = function(x) {
|
||||
return x instanceof Promise && x.constructor === this
|
||||
? x // Refuse to create promises for promises.
|
||||
: new this(function(resolve) { resolve(x); });
|
||||
};
|
||||
|
||||
/**
|
||||
* The Promise.reject function returns a new promise rejected with the
|
||||
* passed argument.
|
||||
*
|
||||
* people.mozilla.org/~jorendorff/es6-draft.html#sec-promise.reject
|
||||
*/
|
||||
Promise.reject = function(r) {
|
||||
return new this(function(_, reject) { reject(r); });
|
||||
};
|
||||
|
||||
var Pp = Promise.prototype;
|
||||
|
||||
/**
|
||||
* When the .then method is called with arguments onFulfilled and
|
||||
* onRejected, the following steps are taken:
|
||||
*
|
||||
* people.mozilla.org/~jorendorff/es6-draft.html#sec-promise.prototype.then
|
||||
*/
|
||||
Pp.then = function(onFulfilled, onRejected) {
|
||||
var capabilityResolve;
|
||||
var capabilityReject;
|
||||
var capabilityPromise = new this.constructor(function(resolve, reject) {
|
||||
capabilityResolve = resolve;
|
||||
capabilityReject = reject;
|
||||
});
|
||||
|
||||
if (typeof capabilityResolve !== "function") {
|
||||
throw new TypeError('Uncallable Promise resolve function');
|
||||
}
|
||||
|
||||
if (typeof capabilityReject !== "function") {
|
||||
throw new TypeError('Uncallable Promise reject function');
|
||||
}
|
||||
|
||||
if (onFulfilled === undefined || onFulfilled === null) {
|
||||
onFulfilled = identity;
|
||||
}
|
||||
|
||||
if (onRejected === undefined || onRejected === null) {
|
||||
onRejected = raise;
|
||||
}
|
||||
|
||||
var promiseSlots = slots(this);
|
||||
var state = promiseSlots.state;
|
||||
if (state === PENDING_STATE) {
|
||||
promiseSlots.fulfillReactions.push(makeReaction(
|
||||
capabilityResolve,
|
||||
capabilityReject,
|
||||
onFulfilled
|
||||
));
|
||||
|
||||
promiseSlots.rejectReactions.push(makeReaction(
|
||||
capabilityResolve,
|
||||
capabilityReject,
|
||||
onRejected
|
||||
));
|
||||
|
||||
} else if (state === FULFILLED_STATE || state === REJECTED_STATE) {
|
||||
setImmediate(makeReaction(
|
||||
capabilityResolve,
|
||||
capabilityReject,
|
||||
state === FULFILLED_STATE ? onFulfilled : onRejected,
|
||||
promiseSlots.result
|
||||
));
|
||||
}
|
||||
|
||||
return capabilityPromise;
|
||||
};
|
||||
|
||||
function makeReaction(resolve, reject, handler, argument) {
|
||||
var hasArgument = arguments.length > 3;
|
||||
return function(result) {
|
||||
try {
|
||||
result = handler(hasArgument ? argument : result);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve(result);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* When the .catch method is called with argument onRejected, the
|
||||
* following steps are taken:
|
||||
*
|
||||
* people.mozilla.org/~jorendorff/es6-draft.html#sec-promise.prototype.catch
|
||||
*/
|
||||
Pp['catch'] = function(onRejected) {
|
||||
return this.then(undefined, onRejected);
|
||||
};
|
||||
|
||||
Pp.toString = function() {
|
||||
return '[object Promise]';
|
||||
};
|
||||
|
||||
return Promise;
|
||||
}(/* jslint evil: true */ Function('return this')()));
|
88
Libraries/vendor/core/Promise.js
vendored
88
Libraries/vendor/core/Promise.js
vendored
@ -1,88 +0,0 @@
|
||||
/**
|
||||
* @generated SignedSource<<a34c32acc93f914fafb29ca64341d514>>
|
||||
*
|
||||
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
* !! This file is a check-in of a static_upstream project! !!
|
||||
* !! !!
|
||||
* !! You should not modify this file directly. Instead: !!
|
||||
* !! 1) Use `fjs use-upstream` to temporarily replace this with !!
|
||||
* !! the latest version from upstream. !!
|
||||
* !! 2) Make your changes, test them, etc. !!
|
||||
* !! 3) Use `fjs push-upstream` to copy your changes back to !!
|
||||
* !! static_upstream. !!
|
||||
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
*
|
||||
* Copyright 2013-2014 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @providesModule Promise
|
||||
*
|
||||
* This module wraps and augments the minimally ES6-compliant Promise
|
||||
* implementation provided by the ES6Promise module.
|
||||
*/
|
||||
|
||||
var Promise = require('ES6Promise');
|
||||
var Pp = Promise.prototype;
|
||||
|
||||
var invariant = require('invariant');
|
||||
var setImmediate = require('setImmediate');
|
||||
var throwImmediate = require('throwImmediate');
|
||||
|
||||
/**
|
||||
* Handle either fulfillment or rejection with the same callback.
|
||||
*/
|
||||
Pp.finally = function(onSettled) {
|
||||
return this.then(onSettled, onSettled);
|
||||
};
|
||||
|
||||
/**
|
||||
* Throw any unhandled error in a separate tick of the event loop.
|
||||
*/
|
||||
Pp.done = function(onFulfilled, onRejected) {
|
||||
this.then(onFulfilled, onRejected).then(null, throwImmediate);
|
||||
};
|
||||
|
||||
/**
|
||||
* This function takes an object with promises as keys and returns a promise.
|
||||
* The returned promise is resolved when all promises from the object are
|
||||
* resolved and gets rejected when the first promise is rejected.
|
||||
*
|
||||
* EXAMPLE:
|
||||
* var promisedMuffin = Promise.allObject({
|
||||
* dough: promisedDough,
|
||||
* frosting: promisedFrosting
|
||||
* }).then(function(results) {
|
||||
* return combine(results.dough, results.frosting);
|
||||
* });
|
||||
*/
|
||||
Promise.allObject = function(/*object*/ promises) {
|
||||
// Throw instead of warn here to make sure people use this only with object.
|
||||
invariant(
|
||||
!Array.isArray(promises),
|
||||
'expected an object, got an array instead'
|
||||
);
|
||||
|
||||
var keys = Object.keys(promises);
|
||||
return Promise.all(keys.map(function(key) {
|
||||
return promises[key];
|
||||
})).then(function(values) {
|
||||
var answers = {};
|
||||
values.forEach(function(value, i) {
|
||||
answers[keys[i]] = value;
|
||||
});
|
||||
return answers;
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = Promise;
|
@ -10,6 +10,7 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTBridgeModule.h"
|
||||
#import "RCTFrameUpdate.h"
|
||||
#import "RCTInvalidating.h"
|
||||
#import "RCTJavaScriptExecutor.h"
|
||||
|
||||
@ -122,4 +123,14 @@ static const char *__rct_import_##module##_##method##__ = #module"."#method;
|
||||
*/
|
||||
- (void)reload;
|
||||
|
||||
/**
|
||||
* Add a new observer that will be called on every screen refresh
|
||||
*/
|
||||
- (void)addFrameUpdateObserver:(id<RCTFrameUpdateObserver>)observer;
|
||||
|
||||
/**
|
||||
* Stop receiving screen refresh updates for the given observer
|
||||
*/
|
||||
- (void)removeFrameUpdateObserver:(id<RCTFrameUpdateObserver>)observer;
|
||||
|
||||
@end
|
||||
|
@ -677,6 +677,73 @@ static NSDictionary *RCTLocalModulesConfig()
|
||||
return localModules;
|
||||
}
|
||||
|
||||
@interface RCTDisplayLink : NSObject <RCTInvalidating>
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
|
||||
@interface RCTBridge (RCTDisplayLink)
|
||||
|
||||
- (void)_update:(CADisplayLink *)displayLink;
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTDisplayLink
|
||||
{
|
||||
__weak RCTBridge *_bridge;
|
||||
CADisplayLink *_displayLink;
|
||||
}
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_bridge = bridge;
|
||||
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_update:)];
|
||||
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)isValid
|
||||
{
|
||||
return _displayLink != nil;
|
||||
}
|
||||
|
||||
- (void)invalidate
|
||||
{
|
||||
if (self.isValid) {
|
||||
[_displayLink invalidate];
|
||||
_displayLink = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_update:(CADisplayLink *)displayLink
|
||||
{
|
||||
[_bridge _update:displayLink];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface RCTFrameUpdate (Private)
|
||||
|
||||
- (instancetype)initWithDisplayLink:(CADisplayLink *)displayLink;
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTFrameUpdate
|
||||
|
||||
- (instancetype)initWithDisplayLink:(CADisplayLink *)displayLink
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_timestamp = displayLink.timestamp;
|
||||
_deltaTime = displayLink.duration;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTBridge
|
||||
{
|
||||
RCTSparseArray *_modulesByID;
|
||||
@ -685,6 +752,8 @@ static NSDictionary *RCTLocalModulesConfig()
|
||||
Class _executorClass;
|
||||
NSURL *_bundleURL;
|
||||
RCTBridgeModuleProviderBlock _moduleProvider;
|
||||
RCTDisplayLink *_displayLink;
|
||||
NSMutableSet *_frameUpdateObservers;
|
||||
BOOL _loading;
|
||||
}
|
||||
|
||||
@ -711,6 +780,8 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
_latestJSExecutor = _javaScriptExecutor;
|
||||
_eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self];
|
||||
_shadowQueue = dispatch_queue_create("com.facebook.React.ShadowQueue", DISPATCH_QUEUE_SERIAL);
|
||||
_displayLink = [[RCTDisplayLink alloc] initWithBridge:self];
|
||||
_frameUpdateObservers = [[NSMutableSet alloc] init];
|
||||
|
||||
// Register passed-in module instances
|
||||
NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init];
|
||||
@ -891,6 +962,9 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
[_javaScriptExecutor invalidate];
|
||||
_javaScriptExecutor = nil;
|
||||
|
||||
[_displayLink invalidate];
|
||||
_frameUpdateObservers = nil;
|
||||
|
||||
// Invalidate modules
|
||||
for (id target in _modulesByID.allObjects) {
|
||||
if ([target respondsToSelector:@selector(invalidate)]) {
|
||||
@ -1075,6 +1149,26 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)_update:(CADisplayLink *)displayLink
|
||||
{
|
||||
RCTFrameUpdate *frameUpdate = [[RCTFrameUpdate alloc] initWithDisplayLink:displayLink];
|
||||
for (id<RCTFrameUpdateObserver> observer in _frameUpdateObservers) {
|
||||
if (![observer respondsToSelector:@selector(isPaused)] || ![observer isPaused]) {
|
||||
[observer didUpdateFrame:frameUpdate];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)addFrameUpdateObserver:(id<RCTFrameUpdateObserver>)observer
|
||||
{
|
||||
[_frameUpdateObservers addObject:observer];
|
||||
}
|
||||
|
||||
- (void)removeFrameUpdateObserver:(id<RCTFrameUpdateObserver>)observer
|
||||
{
|
||||
[_frameUpdateObservers removeObject:observer];
|
||||
}
|
||||
|
||||
- (void)reload
|
||||
{
|
||||
if (!_loading) {
|
||||
|
44
React/Base/RCTFrameUpdate.h
Normal file
44
React/Base/RCTFrameUpdate.h
Normal file
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface containing the information about the last screen refresh.
|
||||
*/
|
||||
@interface RCTFrameUpdate : NSObject
|
||||
|
||||
/**
|
||||
* Timestamp for the actual screen refresh
|
||||
*/
|
||||
@property (nonatomic, readonly) NSTimeInterval timestamp;
|
||||
|
||||
/**
|
||||
* Time since the last frame update ( >= 16.6ms )
|
||||
*/
|
||||
@property (nonatomic, readonly) NSTimeInterval deltaTime;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* Protocol that must be implemented for subscribing to display refreshes (DisplayLink updates)
|
||||
*/
|
||||
@protocol RCTFrameUpdateObserver <NSObject>
|
||||
|
||||
/**
|
||||
* Method called on every screen refresh (if paused != YES)
|
||||
*/
|
||||
- (void)didUpdateFrame:(RCTFrameUpdate *)update;
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
* Synthesize and set to true to pause the calls to -[didUpdateFrame:]
|
||||
*/
|
||||
@property (nonatomic, assign, getter=isPaused) BOOL paused;
|
||||
|
||||
@end
|
@ -106,11 +106,14 @@
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
[_touchHandler invalidate];
|
||||
if (_contentView) {
|
||||
[_bridge enqueueJSCall:@"ReactIOS.unmountComponentAtNodeAndRemoveContainer"
|
||||
args:@[_contentView.reactTag]];
|
||||
}
|
||||
}
|
||||
|
||||
- (UIViewController *)backingViewController {
|
||||
- (UIViewController *)backingViewController
|
||||
{
|
||||
return _backingViewController ?: [super backingViewController];
|
||||
}
|
||||
|
||||
|
@ -10,8 +10,9 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "RCTBridgeModule.h"
|
||||
#import "RCTFrameUpdate.h"
|
||||
#import "RCTInvalidating.h"
|
||||
|
||||
@interface RCTTiming : NSObject <RCTBridgeModule, RCTInvalidating>
|
||||
@interface RCTTiming : NSObject <RCTBridgeModule, RCTInvalidating, RCTFrameUpdateObserver>
|
||||
|
||||
@end
|
||||
|
@ -58,7 +58,6 @@
|
||||
@implementation RCTTiming
|
||||
{
|
||||
RCTSparseArray *_timers;
|
||||
id _updateTimer;
|
||||
}
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
@ -113,32 +112,21 @@ RCT_IMPORT_METHOD(RCTJSTimers, callTimers)
|
||||
|
||||
- (void)stopTimers
|
||||
{
|
||||
[_updateTimer invalidate];
|
||||
_updateTimer = nil;
|
||||
[_bridge removeFrameUpdateObserver:self];
|
||||
}
|
||||
|
||||
- (void)startTimers
|
||||
{
|
||||
RCTAssertMainThread();
|
||||
|
||||
if (![self isValid] || _updateTimer != nil || _timers.count == 0) {
|
||||
if (![self isValid] || _timers.count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
_updateTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(update)];
|
||||
if (_updateTimer) {
|
||||
[_updateTimer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
|
||||
} else {
|
||||
RCTLogWarn(@"Failed to create a display link (probably on buildbot) - using an NSTimer for AppEngine instead.");
|
||||
_updateTimer = [NSTimer scheduledTimerWithTimeInterval:(1.0 / 60)
|
||||
target:self
|
||||
selector:@selector(update)
|
||||
userInfo:nil
|
||||
repeats:YES];
|
||||
}
|
||||
[_bridge addFrameUpdateObserver:self];
|
||||
}
|
||||
|
||||
- (void)update
|
||||
- (void)didUpdateFrame:(RCTFrameUpdate *)update
|
||||
{
|
||||
RCTAssertMainThread();
|
||||
|
||||
|
@ -9,6 +9,8 @@
|
||||
/* Begin PBXBuildFile section */
|
||||
000E6CEB1AB0E980000CDF4D /* RCTSourceCode.m in Sources */ = {isa = PBXBuildFile; fileRef = 000E6CEA1AB0E980000CDF4D /* RCTSourceCode.m */; };
|
||||
00C1A2B31AC0B7E000E89A1C /* RCTDevMenu.m in Sources */ = {isa = PBXBuildFile; fileRef = 00C1A2B21AC0B7E000E89A1C /* RCTDevMenu.m */; };
|
||||
13456E931ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m in Sources */ = {isa = PBXBuildFile; fileRef = 13456E921ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m */; };
|
||||
13456E961ADAD482009F94A7 /* RCTConvert+MapKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 13456E951ADAD482009F94A7 /* RCTConvert+MapKit.m */; };
|
||||
134FCB361A6D42D900051CC8 /* RCTSparseArray.m in Sources */ = {isa = PBXBuildFile; fileRef = 83BEE46D1A6D19BC00B5863B /* RCTSparseArray.m */; };
|
||||
134FCB3D1A6E7F0800051CC8 /* RCTContextExecutor.m in Sources */ = {isa = PBXBuildFile; fileRef = 134FCB3A1A6E7F0800051CC8 /* RCTContextExecutor.m */; };
|
||||
134FCB3E1A6E7F0800051CC8 /* RCTWebViewExecutor.m in Sources */ = {isa = PBXBuildFile; fileRef = 134FCB3C1A6E7F0800051CC8 /* RCTWebViewExecutor.m */; };
|
||||
@ -83,6 +85,10 @@
|
||||
13442BF21AA90E0B0037E5B0 /* RCTAnimationType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAnimationType.h; sourceTree = "<group>"; };
|
||||
13442BF31AA90E0B0037E5B0 /* RCTPointerEvents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPointerEvents.h; sourceTree = "<group>"; };
|
||||
13442BF41AA90E0B0037E5B0 /* RCTViewControllerProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTViewControllerProtocol.h; sourceTree = "<group>"; };
|
||||
13456E911ADAD2DE009F94A7 /* RCTConvert+CoreLocation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RCTConvert+CoreLocation.h"; sourceTree = "<group>"; };
|
||||
13456E921ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTConvert+CoreLocation.m"; sourceTree = "<group>"; };
|
||||
13456E941ADAD482009F94A7 /* RCTConvert+MapKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RCTConvert+MapKit.h"; sourceTree = "<group>"; };
|
||||
13456E951ADAD482009F94A7 /* RCTConvert+MapKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTConvert+MapKit.m"; sourceTree = "<group>"; };
|
||||
134FCB391A6E7F0800051CC8 /* RCTContextExecutor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTContextExecutor.h; sourceTree = "<group>"; };
|
||||
134FCB3A1A6E7F0800051CC8 /* RCTContextExecutor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTContextExecutor.m; sourceTree = "<group>"; };
|
||||
134FCB3B1A6E7F0800051CC8 /* RCTWebViewExecutor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebViewExecutor.h; sourceTree = "<group>"; };
|
||||
@ -148,6 +154,7 @@
|
||||
13E067541A70F44B002CDEE1 /* UIView+React.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+React.m"; sourceTree = "<group>"; };
|
||||
14200DA81AC179B3008EE6BA /* RCTJavaScriptLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTJavaScriptLoader.h; sourceTree = "<group>"; };
|
||||
14200DA91AC179B3008EE6BA /* RCTJavaScriptLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTJavaScriptLoader.m; sourceTree = "<group>"; };
|
||||
1436DD071ADE7AA000A5ED7D /* RCTFrameUpdate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTFrameUpdate.h; sourceTree = "<group>"; };
|
||||
14435CE11AAC4AE100FC20F4 /* RCTMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMap.h; sourceTree = "<group>"; };
|
||||
14435CE21AAC4AE100FC20F4 /* RCTMap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMap.m; sourceTree = "<group>"; };
|
||||
14435CE31AAC4AE100FC20F4 /* RCTMapManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMapManager.h; sourceTree = "<group>"; };
|
||||
@ -255,6 +262,10 @@
|
||||
13C325261AA63B6A0048765F /* RCTAutoInsetsProtocol.h */,
|
||||
58C571C01AA56C1900CDF9C8 /* RCTDatePickerManager.h */,
|
||||
58C571BF1AA56C1900CDF9C8 /* RCTDatePickerManager.m */,
|
||||
13456E911ADAD2DE009F94A7 /* RCTConvert+CoreLocation.h */,
|
||||
13456E921ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m */,
|
||||
13456E941ADAD482009F94A7 /* RCTConvert+MapKit.h */,
|
||||
13456E951ADAD482009F94A7 /* RCTConvert+MapKit.m */,
|
||||
14435CE11AAC4AE100FC20F4 /* RCTMap.h */,
|
||||
14435CE21AAC4AE100FC20F4 /* RCTMap.m */,
|
||||
14435CE31AAC4AE100FC20F4 /* RCTMapManager.h */,
|
||||
@ -381,6 +392,7 @@
|
||||
83CBBA971A6020BB00E9B192 /* RCTTouchHandler.m */,
|
||||
83CBBA4F1A601E3B00E9B192 /* RCTUtils.h */,
|
||||
83CBBA501A601E3B00E9B192 /* RCTUtils.m */,
|
||||
1436DD071ADE7AA000A5ED7D /* RCTFrameUpdate.h */,
|
||||
);
|
||||
path = Base;
|
||||
sourceTree = "<group>";
|
||||
@ -459,6 +471,7 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
13456E961ADAD482009F94A7 /* RCTConvert+MapKit.m in Sources */,
|
||||
13723B501A82FD3C00F88898 /* RCTStatusBarManager.m in Sources */,
|
||||
000E6CEB1AB0E980000CDF4D /* RCTSourceCode.m in Sources */,
|
||||
13B0801E1A69489C00A75B9A /* RCTTextField.m in Sources */,
|
||||
@ -490,6 +503,7 @@
|
||||
83CBBA521A601E3B00E9B192 /* RCTLog.m in Sources */,
|
||||
13B0801D1A69489C00A75B9A /* RCTNavItemManager.m in Sources */,
|
||||
13E067571A70F44B002CDEE1 /* RCTView.m in Sources */,
|
||||
13456E931ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m in Sources */,
|
||||
137327E91AA5CF210034F82E /* RCTTabBarItemManager.m in Sources */,
|
||||
134FCB361A6D42D900051CC8 /* RCTSparseArray.m in Sources */,
|
||||
13A1F71E1A75392D00D3D453 /* RCTKeyCommands.m in Sources */,
|
||||
|
19
React/Views/RCTConvert+CoreLocation.h
Normal file
19
React/Views/RCTConvert+CoreLocation.h
Normal file
@ -0,0 +1,19 @@
|
||||
//
|
||||
// RCTConvert+CoreLocation.h
|
||||
// React
|
||||
//
|
||||
// Created by Nick Lockwood on 12/04/2015.
|
||||
// Copyright (c) 2015 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import <CoreLocation/CoreLocation.h>
|
||||
|
||||
#import "RCTConvert.h"
|
||||
|
||||
@interface RCTConvert (CoreLocation)
|
||||
|
||||
+ (CLLocationDegrees)CLLocationDegrees:(id)json;
|
||||
+ (CLLocationDistance)CLLocationDistance:(id)json;
|
||||
+ (CLLocationCoordinate2D)CLLocationCoordinate2D:(id)json;
|
||||
|
||||
@end
|
25
React/Views/RCTConvert+CoreLocation.m
Normal file
25
React/Views/RCTConvert+CoreLocation.m
Normal file
@ -0,0 +1,25 @@
|
||||
//
|
||||
// RCTConvert+CoreLocation.m
|
||||
// React
|
||||
//
|
||||
// Created by Nick Lockwood on 12/04/2015.
|
||||
// Copyright (c) 2015 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RCTConvert+CoreLocation.h"
|
||||
|
||||
@implementation RCTConvert(CoreLocation)
|
||||
|
||||
RCT_CONVERTER(CLLocationDegrees, CLLocationDegrees, doubleValue);
|
||||
RCT_CONVERTER(CLLocationDistance, CLLocationDistance, doubleValue);
|
||||
|
||||
+ (CLLocationCoordinate2D)CLLocationCoordinate2D:(id)json
|
||||
{
|
||||
json = [self NSDictionary:json];
|
||||
return (CLLocationCoordinate2D){
|
||||
[self CLLocationDegrees:json[@"latitude"]],
|
||||
[self CLLocationDegrees:json[@"longitude"]]
|
||||
};
|
||||
}
|
||||
|
||||
@end
|
22
React/Views/RCTConvert+MapKit.h
Normal file
22
React/Views/RCTConvert+MapKit.h
Normal file
@ -0,0 +1,22 @@
|
||||
//
|
||||
// RCTConvert+MapKit.h
|
||||
// React
|
||||
//
|
||||
// Created by Nick Lockwood on 12/04/2015.
|
||||
// Copyright (c) 2015 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import <MapKit/MapKit.h>
|
||||
|
||||
#import "RCTConvert.h"
|
||||
|
||||
@interface RCTConvert (MapKit)
|
||||
|
||||
+ (MKCoordinateSpan)MKCoordinateSpan:(id)json;
|
||||
+ (MKCoordinateRegion)MKCoordinateRegion:(id)json;
|
||||
+ (MKShape *)MKShape:(id)json;
|
||||
|
||||
typedef NSArray MKShapeArray;
|
||||
+ (MKShapeArray *)MKShapeArray:(id)json;
|
||||
|
||||
@end
|
46
React/Views/RCTConvert+MapKit.m
Normal file
46
React/Views/RCTConvert+MapKit.m
Normal file
@ -0,0 +1,46 @@
|
||||
//
|
||||
// RCTConvert+MapKit.m
|
||||
// React
|
||||
//
|
||||
// Created by Nick Lockwood on 12/04/2015.
|
||||
// Copyright (c) 2015 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RCTConvert+MapKit.h"
|
||||
|
||||
#import "RCTConvert+CoreLocation.h"
|
||||
|
||||
@implementation RCTConvert(MapKit)
|
||||
|
||||
+ (MKCoordinateSpan)MKCoordinateSpan:(id)json
|
||||
{
|
||||
json = [self NSDictionary:json];
|
||||
return (MKCoordinateSpan){
|
||||
[self CLLocationDegrees:json[@"latitudeDelta"]],
|
||||
[self CLLocationDegrees:json[@"longitudeDelta"]]
|
||||
};
|
||||
}
|
||||
|
||||
+ (MKCoordinateRegion)MKCoordinateRegion:(id)json
|
||||
{
|
||||
return (MKCoordinateRegion){
|
||||
[self CLLocationCoordinate2D:json],
|
||||
[self MKCoordinateSpan:json]
|
||||
};
|
||||
}
|
||||
|
||||
+ (MKShape *)MKShape:(id)json
|
||||
{
|
||||
json = [self NSDictionary:json];
|
||||
|
||||
// TODO: more shape types
|
||||
MKShape *shape = [[MKPointAnnotation alloc] init];
|
||||
shape.coordinate = [self CLLocationCoordinate2D:json];
|
||||
shape.title = [RCTConvert NSString:json[@"title"]];
|
||||
shape.subtitle = [RCTConvert NSString:json[@"subtitle"]];
|
||||
return shape;
|
||||
}
|
||||
|
||||
RCT_ARRAY_CONVERTER(MKShape)
|
||||
|
||||
@end
|
@ -10,6 +10,8 @@
|
||||
#import <MapKit/MapKit.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTConvert+MapKit.h"
|
||||
|
||||
extern const CLLocationDegrees RCTMapDefaultSpan;
|
||||
extern const NSTimeInterval RCTMapRegionChangeObserveInterval;
|
||||
extern const CGFloat RCTMapZoomBoundBuffer;
|
||||
@ -19,9 +21,12 @@ extern const CGFloat RCTMapZoomBoundBuffer;
|
||||
@interface RCTMap: MKMapView
|
||||
|
||||
@property (nonatomic, assign) BOOL followUserLocation;
|
||||
@property (nonatomic, assign) BOOL hasStartedLoading;
|
||||
@property (nonatomic, assign) CGFloat minDelta;
|
||||
@property (nonatomic, assign) CGFloat maxDelta;
|
||||
@property (nonatomic, assign) UIEdgeInsets legalLabelInsets;
|
||||
@property (nonatomic, strong) NSTimer *regionChangeObserveTimer;
|
||||
|
||||
- (void)setAnnotations:(MKShapeArray *)annotations;
|
||||
|
||||
@end
|
||||
|
@ -9,7 +9,6 @@
|
||||
|
||||
#import "RCTMap.h"
|
||||
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTEventDispatcher.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTUtils.h"
|
||||
@ -27,10 +26,14 @@ const CGFloat RCTMapZoomBoundBuffer = 0.01;
|
||||
- (instancetype)init
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
|
||||
_hasStartedLoading = NO;
|
||||
|
||||
// Find Apple link label
|
||||
for (UIView *subview in self.subviews) {
|
||||
if ([NSStringFromClass(subview.class) isEqualToString:@"MKAttributionLabel"]) {
|
||||
// This check is super hacky, but the whole premise of moving around Apple's internal subviews is super hacky
|
||||
// This check is super hacky, but the whole premise of moving around
|
||||
// Apple's internal subviews is super hacky
|
||||
_legalLabel = subview;
|
||||
break;
|
||||
}
|
||||
@ -82,11 +85,11 @@ const CGFloat RCTMapZoomBoundBuffer = 0.01;
|
||||
[_locationManager requestWhenInUseAuthorization];
|
||||
}
|
||||
}
|
||||
[super setShowsUserLocation:showsUserLocation];
|
||||
super.showsUserLocation = showsUserLocation;
|
||||
|
||||
// If it needs to show user location, force map view centered
|
||||
// on user's current location on user location updates
|
||||
self.followUserLocation = showsUserLocation;
|
||||
_followUserLocation = showsUserLocation;
|
||||
}
|
||||
}
|
||||
|
||||
@ -109,4 +112,12 @@ const CGFloat RCTMapZoomBoundBuffer = 0.01;
|
||||
[super setRegion:region animated:YES];
|
||||
}
|
||||
|
||||
- (void)setAnnotations:(MKShapeArray *)annotations
|
||||
{
|
||||
[self removeAnnotations:self.annotations];
|
||||
if (annotations.count) {
|
||||
[self addAnnotations:annotations];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -10,43 +10,13 @@
|
||||
#import "RCTMapManager.h"
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTConvert+CoreLocation.h"
|
||||
#import "RCTConvert+MapKit.h"
|
||||
#import "RCTEventDispatcher.h"
|
||||
#import "RCTMap.h"
|
||||
#import "UIView+React.h"
|
||||
|
||||
@implementation RCTConvert(CoreLocation)
|
||||
|
||||
+ (CLLocationCoordinate2D)CLLocationCoordinate2D:(id)json
|
||||
{
|
||||
json = [self NSDictionary:json];
|
||||
return (CLLocationCoordinate2D){
|
||||
[self double:json[@"latitude"]],
|
||||
[self double:json[@"longitude"]]
|
||||
};
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTConvert(MapKit)
|
||||
|
||||
+ (MKCoordinateSpan)MKCoordinateSpan:(id)json
|
||||
{
|
||||
json = [self NSDictionary:json];
|
||||
return (MKCoordinateSpan){
|
||||
[self double:json[@"latitudeDelta"]],
|
||||
[self double:json[@"longitudeDelta"]]
|
||||
};
|
||||
}
|
||||
|
||||
+ (MKCoordinateRegion)MKCoordinateRegion:(id)json
|
||||
{
|
||||
return (MKCoordinateRegion){
|
||||
[self CLLocationCoordinate2D:json],
|
||||
[self MKCoordinateSpan:json]
|
||||
};
|
||||
}
|
||||
|
||||
@end
|
||||
static NSString *const RCTMapViewKey = @"MapView";
|
||||
|
||||
@interface RCTMapManager() <MKMapViewDelegate>
|
||||
|
||||
@ -72,6 +42,8 @@ RCT_EXPORT_VIEW_PROPERTY(maxDelta, CGFloat)
|
||||
RCT_EXPORT_VIEW_PROPERTY(minDelta, CGFloat)
|
||||
RCT_EXPORT_VIEW_PROPERTY(legalLabelInsets, UIEdgeInsets)
|
||||
RCT_EXPORT_VIEW_PROPERTY(region, MKCoordinateRegion)
|
||||
RCT_EXPORT_VIEW_PROPERTY(annotations, MKShapeArray)
|
||||
|
||||
|
||||
#pragma mark MKMapViewDelegate
|
||||
|
||||
@ -93,12 +65,15 @@ RCT_EXPORT_VIEW_PROPERTY(region, MKCoordinateRegion)
|
||||
{
|
||||
[self _regionChanged:mapView];
|
||||
|
||||
if (animated) {
|
||||
mapView.regionChangeObserveTimer = [NSTimer timerWithTimeInterval:RCTMapRegionChangeObserveInterval
|
||||
target:self
|
||||
selector:@selector(_onTick:)
|
||||
userInfo:@{ @"mapView": mapView }
|
||||
userInfo:@{ RCTMapViewKey: mapView }
|
||||
repeats:YES];
|
||||
|
||||
[[NSRunLoop mainRunLoop] addTimer:mapView.regionChangeObserveTimer forMode:NSRunLoopCommonModes];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)mapView:(RCTMap *)mapView regionDidChangeAnimated:(BOOL)animated
|
||||
@ -107,6 +82,17 @@ RCT_EXPORT_VIEW_PROPERTY(region, MKCoordinateRegion)
|
||||
mapView.regionChangeObserveTimer = nil;
|
||||
|
||||
[self _regionChanged:mapView];
|
||||
|
||||
// Don't send region did change events until map has
|
||||
// started loading, as these won't represent the final location
|
||||
if (mapView.hasStartedLoading) {
|
||||
[self _emitRegionChangeEvent:mapView continuous:NO];
|
||||
};
|
||||
}
|
||||
|
||||
- (void)mapViewWillStartLoadingMap:(RCTMap *)mapView
|
||||
{
|
||||
mapView.hasStartedLoading = YES;
|
||||
[self _emitRegionChangeEvent:mapView continuous:NO];
|
||||
}
|
||||
|
||||
@ -114,7 +100,7 @@ RCT_EXPORT_VIEW_PROPERTY(region, MKCoordinateRegion)
|
||||
|
||||
- (void)_onTick:(NSTimer *)timer
|
||||
{
|
||||
[self _regionChanged:timer.userInfo[@"mapView"]];
|
||||
[self _regionChanged:timer.userInfo[RCTMapViewKey]];
|
||||
}
|
||||
|
||||
- (void)_regionChanged:(RCTMap *)mapView
|
||||
|
@ -9,16 +9,17 @@
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTFrameUpdate.h"
|
||||
#import "RCTInvalidating.h"
|
||||
|
||||
@class RCTEventDispatcher;
|
||||
@class RCTBridge;
|
||||
|
||||
@interface RCTNavigator : UIView <RCTInvalidating>
|
||||
@interface RCTNavigator : UIView <RCTFrameUpdateObserver>
|
||||
|
||||
@property (nonatomic, strong) UIView *reactNavSuperviewLink;
|
||||
@property (nonatomic, assign) NSInteger requestedTopOfStack;
|
||||
|
||||
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
* Schedules a JavaScript navigation and prevents `UIKit` from navigating until
|
||||
|
@ -10,6 +10,7 @@
|
||||
#import "RCTNavigator.h"
|
||||
|
||||
#import "RCTAssert.h"
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTEventDispatcher.h"
|
||||
#import "RCTLog.h"
|
||||
@ -190,10 +191,6 @@ NSInteger kNeverProgressed = -10000;
|
||||
@end
|
||||
|
||||
@interface RCTNavigator() <RCTWrapperViewControllerNavigationListener, UINavigationControllerDelegate>
|
||||
{
|
||||
RCTEventDispatcher *_eventDispatcher;
|
||||
NSInteger _numberOfViewControllerMovesToIgnore;
|
||||
}
|
||||
|
||||
@property (nonatomic, assign) NSInteger previousRequestedTopOfStack;
|
||||
|
||||
@ -251,7 +248,6 @@ NSInteger kNeverProgressed = -10000;
|
||||
*
|
||||
*/
|
||||
@property (nonatomic, readonly, assign) CGFloat mostRecentProgress;
|
||||
@property (nonatomic, readwrite, strong) CADisplayLink *displayLink;
|
||||
@property (nonatomic, readonly, strong) NSTimer *runTimer;
|
||||
@property (nonatomic, readonly, assign) NSInteger currentlyTransitioningFrom;
|
||||
@property (nonatomic, readonly, assign) NSInteger currentlyTransitioningTo;
|
||||
@ -263,22 +259,17 @@ NSInteger kNeverProgressed = -10000;
|
||||
@end
|
||||
|
||||
@implementation RCTNavigator
|
||||
{
|
||||
__weak RCTBridge *_bridge;
|
||||
NSInteger _numberOfViewControllerMovesToIgnore;
|
||||
}
|
||||
|
||||
- (id)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
|
||||
- (id)initWithBridge:(RCTBridge *)bridge
|
||||
{
|
||||
if ((self = [super initWithFrame:CGRectZero])) {
|
||||
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(reportNavigationProgress:)];
|
||||
_bridge = bridge;
|
||||
_mostRecentProgress = kNeverProgressed;
|
||||
_dummyView = [[UIView alloc] initWithFrame:CGRectZero];
|
||||
if (_displayLink) {
|
||||
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
|
||||
_displayLink.paused = YES;
|
||||
} else {
|
||||
// It's okay to leak this on a build bot.
|
||||
RCTLogWarn(@"Failed to create a display link (probably on automated build system) - using an NSTimer for AppEngine instead.");
|
||||
_runTimer = [NSTimer scheduledTimerWithTimeInterval:(1.0 / 60.0) target:self selector:@selector(reportNavigationProgress:) userInfo:nil repeats:YES];
|
||||
}
|
||||
_eventDispatcher = eventDispatcher;
|
||||
_previousRequestedTopOfStack = kNeverRequested; // So that we initialize with a push.
|
||||
_previousViews = @[];
|
||||
_currentViews = [[NSMutableArray alloc] initWithCapacity:0];
|
||||
@ -295,7 +286,7 @@ NSInteger kNeverProgressed = -10000;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)reportNavigationProgress:(CADisplayLink *)sender
|
||||
- (void)didUpdateFrame:(RCTFrameUpdate *)update
|
||||
{
|
||||
if (_currentlyTransitioningFrom != _currentlyTransitioningTo) {
|
||||
UIView *topView = _dummyView;
|
||||
@ -307,7 +298,7 @@ NSInteger kNeverProgressed = -10000;
|
||||
return;
|
||||
}
|
||||
_mostRecentProgress = nextProgress;
|
||||
[_eventDispatcher sendInputEventWithName:@"topNavigationProgress" body:@{
|
||||
[_bridge.eventDispatcher sendInputEventWithName:@"topNavigationProgress" body:@{
|
||||
@"fromIndex": @(_currentlyTransitioningFrom),
|
||||
@"toIndex": @(_currentlyTransitioningTo),
|
||||
@"progress": @(nextProgress),
|
||||
@ -350,16 +341,14 @@ NSInteger kNeverProgressed = -10000;
|
||||
_dummyView.frame = (CGRect){{destination}};
|
||||
_currentlyTransitioningFrom = indexOfFrom;
|
||||
_currentlyTransitioningTo = indexOfTo;
|
||||
if (indexOfFrom != indexOfTo) {
|
||||
_displayLink.paused = NO;
|
||||
}
|
||||
[_bridge addFrameUpdateObserver:self];
|
||||
}
|
||||
completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
|
||||
[weakSelf freeLock];
|
||||
_currentlyTransitioningFrom = 0;
|
||||
_currentlyTransitioningTo = 0;
|
||||
_dummyView.frame = CGRectZero;
|
||||
_displayLink.paused = YES;
|
||||
[_bridge removeFrameUpdateObserver:self];
|
||||
// Reset the parallel position tracker
|
||||
}];
|
||||
}
|
||||
@ -400,19 +389,6 @@ NSInteger kNeverProgressed = -10000;
|
||||
return _currentViews;
|
||||
}
|
||||
|
||||
- (BOOL)isValid
|
||||
{
|
||||
return _displayLink != nil;
|
||||
}
|
||||
|
||||
- (void)invalidate
|
||||
{
|
||||
// Prevent displayLink from retaining the navigator indefinitely
|
||||
[_displayLink invalidate];
|
||||
_displayLink = nil;
|
||||
_runTimer = nil;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
@ -430,7 +406,7 @@ NSInteger kNeverProgressed = -10000;
|
||||
|
||||
- (void)handleTopOfStackChanged
|
||||
{
|
||||
[_eventDispatcher sendInputEventWithName:@"topNavigateBack" body:@{
|
||||
[_bridge.eventDispatcher sendInputEventWithName:@"topNavigateBack" body:@{
|
||||
@"target":self.reactTag,
|
||||
@"stackLength":@(_navigationController.viewControllers.count)
|
||||
}];
|
||||
@ -438,7 +414,7 @@ NSInteger kNeverProgressed = -10000;
|
||||
|
||||
- (void)dispatchFakeScrollEvent
|
||||
{
|
||||
[_eventDispatcher sendScrollEventWithType:RCTScrollEventTypeMove
|
||||
[_bridge.eventDispatcher sendScrollEventWithType:RCTScrollEventTypeMove
|
||||
reactTag:self.reactTag
|
||||
scrollView:nil
|
||||
userData:nil];
|
||||
@ -494,21 +470,24 @@ NSInteger kNeverProgressed = -10000;
|
||||
jsMakingNoProgressAndDoesntNeedTo)) {
|
||||
RCTLogError(@"JS has only made partial progress to catch up to UIKit");
|
||||
}
|
||||
RCTAssert(
|
||||
currentReactCount <= _currentViews.count,
|
||||
@"Cannot adjust current top of stack beyond available views"
|
||||
);
|
||||
if (currentReactCount > _currentViews.count) {
|
||||
RCTLogError(@"Cannot adjust current top of stack beyond available views");
|
||||
}
|
||||
|
||||
// Views before the previous react count must not have changed. Views greater than previousReactCount
|
||||
// up to currentReactCount may have changed.
|
||||
for (NSInteger i = 0; i < MIN(_currentViews.count, MIN(_previousViews.count, previousReactCount)); i++) {
|
||||
RCTAssert(_currentViews[i] == _previousViews[i], @"current view should equal previous view");
|
||||
if (_currentViews[i] != _previousViews[i]) {
|
||||
RCTLogError(@"current view should equal previous view");
|
||||
}
|
||||
}
|
||||
if (currentReactCount < 1) {
|
||||
RCTLogError(@"should be at least one current view");
|
||||
}
|
||||
RCTAssert(currentReactCount >= 1, @"should be at least one current view");
|
||||
if (jsGettingAhead) {
|
||||
if (reactPushOne) {
|
||||
UIView *lastView = [_currentViews lastObject];
|
||||
RCTWrapperViewController *vc = [[RCTWrapperViewController alloc] initWithNavItem:(RCTNavItem *)lastView eventDispatcher:_eventDispatcher];
|
||||
RCTWrapperViewController *vc = [[RCTWrapperViewController alloc] initWithNavItem:(RCTNavItem *)lastView eventDispatcher:_bridge.eventDispatcher];
|
||||
vc.navigationListener = self;
|
||||
_numberOfViewControllerMovesToIgnore = 1;
|
||||
[_navigationController pushViewController:vc animated:(currentReactCount > 1)];
|
||||
@ -517,7 +496,7 @@ NSInteger kNeverProgressed = -10000;
|
||||
_numberOfViewControllerMovesToIgnore = viewControllerCount - currentReactCount;
|
||||
[_navigationController popToViewController:viewControllerToPopTo animated:YES];
|
||||
} else {
|
||||
RCTAssert(NO, @"Pushing or popping more than one view at a time from JS");
|
||||
RCTLogError(@"Pushing or popping more than one view at a time from JS");
|
||||
}
|
||||
} else if (jsCatchingUp) {
|
||||
[self freeLock]; // Nothing to push/pop
|
||||
|
@ -21,7 +21,7 @@ RCT_EXPORT_MODULE()
|
||||
|
||||
- (UIView *)view
|
||||
{
|
||||
return [[RCTNavigator alloc] initWithEventDispatcher:self.bridge.eventDispatcher];
|
||||
return [[RCTNavigator alloc] initWithBridge:self.bridge];
|
||||
}
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(requestedTopOfStack, NSInteger)
|
||||
|
@ -44,7 +44,6 @@
|
||||
@"search": @(UITabBarSystemItemSearch),
|
||||
@"top-rated": @(UITabBarSystemItemTopRated),
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
// Update icon
|
||||
|
@ -15,6 +15,7 @@
|
||||
|
||||
@property (nonatomic, assign) BOOL caretHidden;
|
||||
@property (nonatomic, assign) BOOL autoCorrect;
|
||||
@property (nonatomic, assign) BOOL selectTextOnFocus;
|
||||
@property (nonatomic, assign) UIEdgeInsets contentInset;
|
||||
|
||||
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
|
||||
|
@ -104,15 +104,26 @@
|
||||
}
|
||||
|
||||
RCT_TEXT_EVENT_HANDLER(_textFieldDidChange, RCTTextEventTypeChange)
|
||||
RCT_TEXT_EVENT_HANDLER(_textFieldBeginEditing, RCTTextEventTypeFocus)
|
||||
RCT_TEXT_EVENT_HANDLER(_textFieldEndEditing, RCTTextEventTypeEnd)
|
||||
RCT_TEXT_EVENT_HANDLER(_textFieldSubmitEditing, RCTTextEventTypeSubmit)
|
||||
|
||||
- (void)_textFieldBeginEditing
|
||||
{
|
||||
if (_selectTextOnFocus) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self selectAll:nil];
|
||||
});
|
||||
}
|
||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus
|
||||
reactTag:self.reactTag
|
||||
text:self.text];
|
||||
}
|
||||
|
||||
// TODO: we should support shouldChangeTextInRect (see UITextFieldDelegate)
|
||||
|
||||
- (BOOL)becomeFirstResponder
|
||||
{
|
||||
_jsRequestingFirstResponder = YES; // TODO: is this still needed?
|
||||
_jsRequestingFirstResponder = YES;
|
||||
BOOL result = [super becomeFirstResponder];
|
||||
_jsRequestingFirstResponder = NO;
|
||||
return result;
|
||||
|
@ -30,6 +30,8 @@ RCT_EXPORT_VIEW_PROPERTY(enabled, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(placeholder, NSString)
|
||||
RCT_EXPORT_VIEW_PROPERTY(text, NSString)
|
||||
RCT_EXPORT_VIEW_PROPERTY(clearButtonMode, UITextFieldViewMode)
|
||||
RCT_REMAP_VIEW_PROPERTY(clearTextOnFocus, clearsOnBeginEditing, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(selectTextOnFocus, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(keyboardType, UIKeyboardType)
|
||||
RCT_EXPORT_VIEW_PROPERTY(returnKeyType, UIReturnKeyType)
|
||||
RCT_EXPORT_VIEW_PROPERTY(enablesReturnKeyAutomatically, BOOL)
|
||||
|
Loading…
x
Reference in New Issue
Block a user