mirror of
https://github.com/status-im/react-native.git
synced 2025-02-26 08:05:34 +00:00
Updates from Fri 24 Apr
This commit is contained in:
commit
21b4b5b352
@ -22,24 +22,33 @@
|
||||
{
|
||||
NSURL *jsCodeLocation;
|
||||
|
||||
// Loading JavaScript code - uncomment the one you want.
|
||||
/**
|
||||
* Loading JavaScript code - uncomment the one you want.
|
||||
*
|
||||
* OPTION 1
|
||||
* Load from development server. Start the server from the repository root:
|
||||
*
|
||||
* $ npm start
|
||||
*
|
||||
* To run on device, change `localhost` to the IP address of your computer
|
||||
* (you can get this by typing `ifconfig` into the terminal and selecting the
|
||||
* `inet` value under `en0:`) and make sure your computer and iOS device are
|
||||
* on the same Wi-Fi network.
|
||||
*/
|
||||
|
||||
// OPTION 1
|
||||
// Load from development server. Start the server from the repository root:
|
||||
//
|
||||
// $ npm start
|
||||
//
|
||||
// To run on device, change `localhost` to the IP address of your computer, and make sure your computer and
|
||||
// iOS device are on the same Wi-Fi network.
|
||||
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/2048/Game2048.includeRequire.runModule.bundle"];
|
||||
|
||||
// OPTION 2
|
||||
// Load from pre-bundled file on disk. To re-generate the static bundle, run
|
||||
//
|
||||
// $ curl http://localhost:8081/Examples/2048/Game2048.includeRequire.runModule.bundle -o main.jsbundle
|
||||
//
|
||||
// and uncomment the next following line
|
||||
// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
|
||||
/**
|
||||
* OPTION 2
|
||||
* Load from pre-bundled file on disk. To re-generate the static bundle, `cd`
|
||||
* to your Xcode project folder in the terminal, and run
|
||||
*
|
||||
* $ curl 'http://localhost:8081/Examples/2048/Game2048.includeRequire.runModule.bundle' -o main.jsbundle
|
||||
*
|
||||
* then add the `main.jsbundle` file to your project and uncomment this line:
|
||||
*/
|
||||
|
||||
// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
|
||||
|
||||
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
|
||||
moduleName:@"Game2048"
|
||||
|
@ -23,24 +23,33 @@
|
||||
{
|
||||
NSURL *jsCodeLocation;
|
||||
|
||||
// Loading JavaScript code - uncomment the one you want.
|
||||
/**
|
||||
* Loading JavaScript code - uncomment the one you want.
|
||||
*
|
||||
* OPTION 1
|
||||
* Load from development server. Start the server from the repository root:
|
||||
*
|
||||
* $ npm start
|
||||
*
|
||||
* To run on device, change `localhost` to the IP address of your computer
|
||||
* (you can get this by typing `ifconfig` into the terminal and selecting the
|
||||
* `inet` value under `en0:`) and make sure your computer and iOS device are
|
||||
* on the same Wi-Fi network.
|
||||
*/
|
||||
|
||||
// OPTION 1
|
||||
// Load from development server. Start the server from the repository root:
|
||||
//
|
||||
// $ npm start
|
||||
//
|
||||
// To run on device, change `localhost` to the IP address of your computer, and make sure your computer and
|
||||
// iOS device are on the same Wi-Fi network.
|
||||
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/Movies/MoviesApp.includeRequire.runModule.bundle"];
|
||||
|
||||
// OPTION 2
|
||||
// Load from pre-bundled file on disk. To re-generate the static bundle, run
|
||||
//
|
||||
// $ curl http://localhost:8081/Examples/Movies/MoviesApp.includeRequire.runModule.bundle -o main.jsbundle
|
||||
//
|
||||
// and uncomment the next following line
|
||||
// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
|
||||
/**
|
||||
* OPTION 2
|
||||
* Load from pre-bundled file on disk. To re-generate the static bundle, `cd`
|
||||
* to your Xcode project folder in the terminal, and run
|
||||
*
|
||||
* $ curl 'http://localhost:8081/Examples/Movies/MoviesApp.includeRequire.runModule.bundle' -o main.jsbundle
|
||||
*
|
||||
* then add the `main.jsbundle` file to your project and uncomment this line:
|
||||
*/
|
||||
|
||||
// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
|
||||
|
||||
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
|
||||
moduleName:@"MoviesApp"
|
||||
|
@ -17,25 +17,33 @@
|
||||
{
|
||||
NSURL *jsCodeLocation;
|
||||
|
||||
// Loading JavaScript code - uncomment the one you want.
|
||||
/**
|
||||
* Loading JavaScript code - uncomment the one you want.
|
||||
*
|
||||
* OPTION 1
|
||||
* Load from development server. Start the server from the repository root:
|
||||
*
|
||||
* $ npm start
|
||||
*
|
||||
* To run on device, change `localhost` to the IP address of your computer
|
||||
* (you can get this by typing `ifconfig` into the terminal and selecting the
|
||||
* `inet` value under `en0:`) and make sure your computer and iOS device are
|
||||
* on the same Wi-Fi network.
|
||||
*/
|
||||
|
||||
// OPTION 1
|
||||
// Load from development server. Start the server from the repository root:
|
||||
//
|
||||
// $ npm start
|
||||
//
|
||||
// To run on device, change `localhost` to the IP address of your computer, and make sure your computer and
|
||||
// iOS device are on the same Wi-Fi network.
|
||||
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/SampleApp/index.ios.bundle"];
|
||||
|
||||
// OPTION 2
|
||||
// Load from pre-bundled file on disk. To re-generate the static bundle,
|
||||
// from the root of your project directory, run
|
||||
//
|
||||
// $ react-native bundle
|
||||
//
|
||||
// and uncomment the next following line
|
||||
// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
|
||||
/**
|
||||
* OPTION 2
|
||||
* Load from pre-bundled file on disk. To re-generate the static bundle
|
||||
* from the root of your project directory, run
|
||||
*
|
||||
* $ react-native bundle --minify
|
||||
*
|
||||
* see http://facebook.github.io/react-native/docs/runningondevice.html
|
||||
*/
|
||||
|
||||
// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
|
||||
|
||||
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
|
||||
moduleName:@"SampleApp"
|
||||
|
@ -1,5 +1,8 @@
|
||||
// Offline JS
|
||||
// To re-generate the offline bundle, run this from root of your project
|
||||
// $ curl 'http://localhost:8081/Examples/SampleApp/index.ios.bundle?dev=false&minify=true' -o iOS/main.jsbundle
|
||||
// To re-generate the offline bundle, run this from the root of your project:
|
||||
//
|
||||
// $ react-native bundle --minify
|
||||
//
|
||||
// See http://facebook.github.io/react-native/docs/runningondevice.html for more details.
|
||||
|
||||
throw new Error('Offline JS file is empty. See iOS/main.jsbundle for instructions');
|
||||
|
@ -22,24 +22,33 @@
|
||||
{
|
||||
NSURL *jsCodeLocation;
|
||||
|
||||
// Loading JavaScript code - uncomment the one you want.
|
||||
/**
|
||||
* Loading JavaScript code - uncomment the one you want.
|
||||
*
|
||||
* OPTION 1
|
||||
* Load from development server. Start the server from the repository root:
|
||||
*
|
||||
* $ npm start
|
||||
*
|
||||
* To run on device, change `localhost` to the IP address of your computer
|
||||
* (you can get this by typing `ifconfig` into the terminal and selecting the
|
||||
* `inet` value under `en0:`) and make sure your computer and iOS device are
|
||||
* on the same Wi-Fi network.
|
||||
*/
|
||||
|
||||
// OPTION 1
|
||||
// Load from development server. Start the server from the repository root:
|
||||
//
|
||||
// $ npm start
|
||||
//
|
||||
// To run on device, change `localhost` to the IP address of your computer, and make sure your computer and
|
||||
// iOS device are on the same Wi-Fi network.
|
||||
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/TicTacToe/TicTacToeApp.includeRequire.runModule.bundle"];
|
||||
|
||||
// OPTION 2
|
||||
// Load from pre-bundled file on disk. To re-generate the static bundle, run
|
||||
//
|
||||
// $ curl http://localhost:8081/Examples/TicTacToe/TicTacToeApp.includeRequire.runModule.bundle -o main.jsbundle
|
||||
//
|
||||
// and uncomment the next following line
|
||||
// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
|
||||
/**
|
||||
* OPTION 2
|
||||
* Load from pre-bundled file on disk. To re-generate the static bundle, `cd`
|
||||
* to your Xcode project folder in the terminal, and run
|
||||
*
|
||||
* $ curl 'http://localhost:8081/Examples/TicTacToe/TicTacToeApp.includeRequire.runModule.bundle' -o main.jsbundle
|
||||
*
|
||||
* then add the `main.jsbundle` file to your project and uncomment this line:
|
||||
*/
|
||||
|
||||
// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
|
||||
|
||||
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
|
||||
moduleName:@"TicTacToeApp"
|
||||
|
@ -175,4 +175,6 @@ var styles = StyleSheet.create({
|
||||
}
|
||||
});
|
||||
|
||||
TabBarExample.external = true;
|
||||
|
||||
module.exports = TabBarExample;
|
||||
|
90
Examples/UIExplorer/NavigatorIOSColorsExample.js
Normal file
90
Examples/UIExplorer/NavigatorIOSColorsExample.js
Normal file
@ -0,0 +1,90 @@
|
||||
/**
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* Facebook reserves all rights not expressly granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var React = require('react-native');
|
||||
var {
|
||||
NavigatorIOS,
|
||||
StatusBarIOS,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View
|
||||
} = React;
|
||||
|
||||
var EmptyPage = React.createClass({
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<View style={styles.emptyPage}>
|
||||
<Text style={styles.emptyPageText}>
|
||||
{this.props.text}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
var NavigatorIOSColors = React.createClass({
|
||||
|
||||
statics: {
|
||||
title: '<NavigatorIOS> - Custom',
|
||||
description: 'iOS navigation with custom nav bar colors',
|
||||
},
|
||||
|
||||
render: function() {
|
||||
// Set StatusBar with light contents to get better contrast
|
||||
StatusBarIOS.setStyle(StatusBarIOS.Style['lightContent']);
|
||||
|
||||
return (
|
||||
<NavigatorIOS
|
||||
style={styles.container}
|
||||
initialRoute={{
|
||||
component: EmptyPage,
|
||||
title: '<NavigatorIOS>',
|
||||
rightButtonTitle: 'Done',
|
||||
onRightButtonPress: () => {
|
||||
StatusBarIOS.setStyle(StatusBarIOS.Style['default']);
|
||||
this.props.onExampleExit();
|
||||
},
|
||||
passProps: {
|
||||
text: 'The nav bar has custom colors with tintColor, ' +
|
||||
'barTintColor and titleTextColor props.',
|
||||
},
|
||||
}}
|
||||
tintColor="#FFFFFF"
|
||||
barTintColor="#183E63"
|
||||
titleTextColor="#FFFFFF"
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
emptyPage: {
|
||||
flex: 1,
|
||||
paddingTop: 64,
|
||||
},
|
||||
emptyPageText: {
|
||||
margin: 10,
|
||||
},
|
||||
});
|
||||
|
||||
NavigatorIOSColors.external = true;
|
||||
|
||||
module.exports = NavigatorIOSColors;
|
@ -131,12 +131,12 @@ exports.description = 'Monitor network status';
|
||||
exports.examples = [
|
||||
{
|
||||
title: 'NetInfo.isConnected',
|
||||
description: 'Asyncronously load and observe connectivity',
|
||||
description: 'Asynchronously load and observe connectivity',
|
||||
render(): ReactElement { return <IsConnected />; }
|
||||
},
|
||||
{
|
||||
title: 'NetInfo.reachabilityIOS',
|
||||
description: 'Asyncronously load and observe iOS reachability',
|
||||
description: 'Asynchronously load and observe iOS reachability',
|
||||
render(): ReactElement { return <ReachabilityCurrent />; }
|
||||
},
|
||||
{
|
||||
|
@ -22,24 +22,33 @@
|
||||
{
|
||||
NSURL *jsCodeLocation;
|
||||
|
||||
// Loading JavaScript code - uncomment the one you want.
|
||||
/**
|
||||
* Loading JavaScript code - uncomment the one you want.
|
||||
*
|
||||
* OPTION 1
|
||||
* Load from development server. Start the server from the repository root:
|
||||
*
|
||||
* $ npm start
|
||||
*
|
||||
* To run on device, change `localhost` to the IP address of your computer
|
||||
* (you can get this by typing `ifconfig` into the terminal and selecting the
|
||||
* `inet` value under `en0:`) and make sure your computer and iOS device are
|
||||
* on the same Wi-Fi network.
|
||||
*/
|
||||
|
||||
// OPTION 1
|
||||
// Load from development server. Start the server from the repository root:
|
||||
//
|
||||
// $ npm start
|
||||
//
|
||||
// To run on device, change `localhost` to the IP address of your computer, and make sure your computer and
|
||||
// iOS device are on the same Wi-Fi network.
|
||||
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/UIExplorer/UIExplorerApp.includeRequire.runModule.bundle?dev=true"];
|
||||
|
||||
// OPTION 2
|
||||
// Load from pre-bundled file on disk. To re-generate the static bundle, run
|
||||
//
|
||||
// $ curl http://localhost:8081/Examples/UIExplorer/UIExplorerApp.includeRequire.runModule.bundle -o main.jsbundle
|
||||
//
|
||||
// and uncomment the next following line
|
||||
// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
|
||||
/**
|
||||
* OPTION 2
|
||||
* Load from pre-bundled file on disk. To re-generate the static bundle, `cd`
|
||||
* to your Xcode project folder and run
|
||||
*
|
||||
* $ curl 'http://localhost:8081/Examples/UIExplorer/UIExplorerApp.includeRequire.runModule.bundle' -o main.jsbundle
|
||||
*
|
||||
* then add the `main.jsbundle` file to your project and uncomment this line:
|
||||
*/
|
||||
|
||||
// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
|
||||
|
||||
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
|
||||
moduleName:@"UIExplorerApp"
|
||||
|
@ -26,7 +26,6 @@ var {
|
||||
TouchableHighlight,
|
||||
View,
|
||||
} = React;
|
||||
var NavigatorExample = require('./Navigator/NavigatorExample');
|
||||
|
||||
var { TestModule } = React.addons;
|
||||
|
||||
@ -39,7 +38,8 @@ var COMPONENTS = [
|
||||
require('./ListViewExample'),
|
||||
require('./ListViewPagingExample'),
|
||||
require('./MapViewExample'),
|
||||
NavigatorExample,
|
||||
require('./Navigator/NavigatorExample'),
|
||||
require('./NavigatorIOSColorsExample'),
|
||||
require('./NavigatorIOSExample'),
|
||||
require('./PickerIOSExample'),
|
||||
require('./ScrollViewExample'),
|
||||
@ -181,10 +181,8 @@ class UIExplorerList extends React.Component {
|
||||
}
|
||||
|
||||
_onPressRow(example) {
|
||||
if (example === NavigatorExample) {
|
||||
this.props.onExternalExampleRequested(
|
||||
NavigatorExample
|
||||
);
|
||||
if (example.external) {
|
||||
this.props.onExternalExampleRequested(example);
|
||||
return;
|
||||
}
|
||||
var Component = makeRenderable(example);
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 27 KiB |
@ -59,11 +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 because the other tests will tear out the rootView
|
||||
- (void)testAAA_RootViewLoadsAndRenders
|
||||
{
|
||||
UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
|
||||
UIViewController *vc = [UIApplication sharedApplication].delegate.window.rootViewController;
|
||||
RCTAssert([vc.view isKindOfClass:[RCTRootView class]], @"This test must run first.");
|
||||
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
|
||||
BOOL foundElement = NO;
|
||||
@ -72,10 +71,8 @@
|
||||
while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) {
|
||||
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date];
|
||||
[[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:date];
|
||||
|
||||
redboxError = [[RCTRedBox sharedInstance] currentErrorMessage];
|
||||
|
||||
foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) {
|
||||
foundElement = [self findSubviewInView:vc.view matching:^(UIView *view) {
|
||||
if ([view respondsToSelector:@selector(attributedText)]) {
|
||||
NSString *text = [(id)view attributedText].string;
|
||||
if ([text isEqualToString:@"<View>"]) {
|
||||
@ -120,6 +117,7 @@
|
||||
[_runner runTest:_cmd module:@"TabBarExample"];
|
||||
}
|
||||
|
||||
// Make sure this test runs last
|
||||
- (void)testZZZ_NotInRecordMode
|
||||
{
|
||||
RCTAssert(_runner.recordMode == NO, @"Don't forget to turn record mode back to NO before commit.");
|
||||
|
@ -198,6 +198,7 @@
|
||||
58B511F01A9E6C8500147676 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_STATIC_ANALYZER_MODE = deep;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
||||
@ -206,6 +207,7 @@
|
||||
LIBRARY_SEARCH_PATHS = "$(inherited)";
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = RCTActionSheet;
|
||||
RUN_CLANG_STATIC_ANALYZER = YES;
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Debug;
|
||||
@ -213,6 +215,7 @@
|
||||
58B511F11A9E6C8500147676 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_STATIC_ANALYZER_MODE = deep;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
||||
@ -221,6 +224,7 @@
|
||||
LIBRARY_SEARCH_PATHS = "$(inherited)";
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = RCTActionSheet;
|
||||
RUN_CLANG_STATIC_ANALYZER = NO;
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Release;
|
||||
|
@ -30,96 +30,97 @@ RCT_EXPORT_MODULE()
|
||||
return self;
|
||||
}
|
||||
|
||||
- (dispatch_queue_t)methodQueue
|
||||
{
|
||||
return dispatch_get_main_queue();
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(showActionSheetWithOptions:(NSDictionary *)options
|
||||
failureCallback:(RCTResponseSenderBlock)failureCallback
|
||||
successCallback:(RCTResponseSenderBlock)successCallback)
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
UIActionSheet *actionSheet = [[UIActionSheet alloc] init];
|
||||
UIActionSheet *actionSheet = [[UIActionSheet alloc] init];
|
||||
|
||||
actionSheet.title = options[@"title"];
|
||||
actionSheet.title = options[@"title"];
|
||||
|
||||
for (NSString *option in options[@"options"]) {
|
||||
[actionSheet addButtonWithTitle:option];
|
||||
}
|
||||
for (NSString *option in options[@"options"]) {
|
||||
[actionSheet addButtonWithTitle:option];
|
||||
}
|
||||
|
||||
if (options[@"destructiveButtonIndex"]) {
|
||||
actionSheet.destructiveButtonIndex = [options[@"destructiveButtonIndex"] integerValue];
|
||||
}
|
||||
if (options[@"cancelButtonIndex"]) {
|
||||
actionSheet.cancelButtonIndex = [options[@"cancelButtonIndex"] integerValue];
|
||||
}
|
||||
if (options[@"destructiveButtonIndex"]) {
|
||||
actionSheet.destructiveButtonIndex = [options[@"destructiveButtonIndex"] integerValue];
|
||||
}
|
||||
if (options[@"cancelButtonIndex"]) {
|
||||
actionSheet.cancelButtonIndex = [options[@"cancelButtonIndex"] integerValue];
|
||||
}
|
||||
|
||||
actionSheet.delegate = self;
|
||||
actionSheet.delegate = self;
|
||||
|
||||
_callbacks[keyForInstance(actionSheet)] = successCallback;
|
||||
_callbacks[RCTKeyForInstance(actionSheet)] = successCallback;
|
||||
|
||||
UIWindow *appWindow = [[[UIApplication sharedApplication] delegate] window];
|
||||
if (appWindow == nil) {
|
||||
RCTLogError(@"Tried to display action sheet but there is no application window. options: %@", options);
|
||||
return;
|
||||
}
|
||||
[actionSheet showInView:appWindow];
|
||||
});
|
||||
UIWindow *appWindow = [[[UIApplication sharedApplication] delegate] window];
|
||||
if (appWindow == nil) {
|
||||
RCTLogError(@"Tried to display action sheet but there is no application window. options: %@", options);
|
||||
return;
|
||||
}
|
||||
[actionSheet showInView:appWindow];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(showShareActionSheetWithOptions:(NSDictionary *)options
|
||||
failureCallback:(RCTResponseSenderBlock)failureCallback
|
||||
successCallback:(RCTResponseSenderBlock)successCallback)
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSMutableArray *items = [NSMutableArray array];
|
||||
id message = options[@"message"];
|
||||
id url = options[@"url"];
|
||||
if ([message isKindOfClass:[NSString class]]) {
|
||||
[items addObject:message];
|
||||
}
|
||||
if ([url isKindOfClass:[NSString class]]) {
|
||||
[items addObject:[NSURL URLWithString:url]];
|
||||
}
|
||||
if ([items count] == 0) {
|
||||
failureCallback(@[@"No `url` or `message` to share"]);
|
||||
return;
|
||||
}
|
||||
UIActivityViewController *share = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:nil];
|
||||
UIViewController *ctrl = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
|
||||
if ([share respondsToSelector:@selector(setCompletionWithItemsHandler:)]) {
|
||||
share.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
|
||||
if (activityError) {
|
||||
failureCallback(@[[activityError localizedDescription]]);
|
||||
} else {
|
||||
successCallback(@[@(completed), (activityType ?: [NSNull null])]);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
NSMutableArray *items = [NSMutableArray array];
|
||||
id message = options[@"message"];
|
||||
id url = options[@"url"];
|
||||
if ([message isKindOfClass:[NSString class]]) {
|
||||
[items addObject:message];
|
||||
}
|
||||
if ([url isKindOfClass:[NSString class]]) {
|
||||
[items addObject:[NSURL URLWithString:url]];
|
||||
}
|
||||
if ([items count] == 0) {
|
||||
failureCallback(@[@"No `url` or `message` to share"]);
|
||||
return;
|
||||
}
|
||||
UIActivityViewController *share = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:nil];
|
||||
UIViewController *ctrl = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
|
||||
if ([share respondsToSelector:@selector(setCompletionWithItemsHandler:)]) {
|
||||
share.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
|
||||
if (activityError) {
|
||||
failureCallback(@[[activityError localizedDescription]]);
|
||||
} else {
|
||||
successCallback(@[@(completed), (activityType ?: [NSNull null])]);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
|
||||
#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0
|
||||
|
||||
if (![UIActivityViewController instancesRespondToSelector:@selector(completionWithItemsHandler)]) {
|
||||
// Legacy iOS 7 implementation
|
||||
share.completionHandler = ^(NSString *activityType, BOOL completed) {
|
||||
successCallback(@[@(completed), (activityType ?: [NSNull null])]);
|
||||
};
|
||||
} else
|
||||
if (![UIActivityViewController instancesRespondToSelector:@selector(completionWithItemsHandler)]) {
|
||||
// Legacy iOS 7 implementation
|
||||
share.completionHandler = ^(NSString *activityType, BOOL completed) {
|
||||
successCallback(@[@(completed), (activityType ?: [NSNull null])]);
|
||||
};
|
||||
} else
|
||||
|
||||
#endif
|
||||
|
||||
{
|
||||
// iOS 8 version
|
||||
share.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
|
||||
successCallback(@[@(completed), (activityType ?: [NSNull null])]);
|
||||
};
|
||||
}
|
||||
{
|
||||
// iOS 8 version
|
||||
share.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
|
||||
successCallback(@[@(completed), (activityType ?: [NSNull null])]);
|
||||
};
|
||||
}
|
||||
[ctrl presentViewController:share animated:YES completion:nil];
|
||||
});
|
||||
}
|
||||
[ctrl presentViewController:share animated:YES completion:nil];
|
||||
}
|
||||
|
||||
#pragma mark UIActionSheetDelegate Methods
|
||||
|
||||
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
|
||||
{
|
||||
NSString *key = keyForInstance(actionSheet);
|
||||
NSString *key = RCTKeyForInstance(actionSheet);
|
||||
RCTResponseSenderBlock callback = _callbacks[key];
|
||||
if (callback) {
|
||||
callback(@[@(buttonIndex)]);
|
||||
@ -133,7 +134,7 @@ RCT_EXPORT_METHOD(showShareActionSheetWithOptions:(NSDictionary *)options
|
||||
|
||||
#pragma mark Private
|
||||
|
||||
NS_INLINE NSString *keyForInstance(id instance)
|
||||
static NSString *RCTKeyForInstance(id instance)
|
||||
{
|
||||
return [NSString stringWithFormat:@"%p", instance];
|
||||
}
|
||||
|
@ -208,6 +208,7 @@
|
||||
832C81951AAF6DF0007FA2F7 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_STATIC_ANALYZER_MODE = deep;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
||||
@ -215,6 +216,7 @@
|
||||
);
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
RUN_CLANG_STATIC_ANALYZER = YES;
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Debug;
|
||||
@ -222,6 +224,7 @@
|
||||
832C81961AAF6DF0007FA2F7 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_STATIC_ANALYZER_MODE = deep;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
||||
|
@ -20,6 +20,9 @@
|
||||
type EasingFunction = (t: number) => number;
|
||||
|
||||
var defaults = {
|
||||
linear: function(t: number): number {
|
||||
return t;
|
||||
},
|
||||
easeInQuad: function(t: number): number {
|
||||
return t * t;
|
||||
},
|
||||
|
@ -68,6 +68,11 @@ RCT_EXPORT_MODULE()
|
||||
return self;
|
||||
}
|
||||
|
||||
- (dispatch_queue_t)methodQueue
|
||||
{
|
||||
return _bridge.uiManager.methodQueue;
|
||||
}
|
||||
|
||||
- (id (^)(CGFloat))interpolateFrom:(CGFloat[])fromArray to:(CGFloat[])toArray count:(NSUInteger)count typeName:(const char *)typeName
|
||||
{
|
||||
if (count == 1) {
|
||||
@ -259,7 +264,9 @@ RCT_EXPORT_METHOD(stopAnimation:(NSNumber *)animationTag)
|
||||
RCTAnimationExperimentalManager *strongSelf = weakSelf;
|
||||
|
||||
NSNumber *reactTag = strongSelf->_animationRegistry[animationTag];
|
||||
if (!reactTag) return;
|
||||
if (!reactTag) {
|
||||
return;
|
||||
}
|
||||
|
||||
UIView *view = viewRegistry[reactTag];
|
||||
for (NSString *animationKey in view.layer.animationKeys) {
|
||||
|
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var NativeModules = {
|
||||
I18n: {
|
||||
translationsDictionary: {
|
||||
'Good bye, {name}!|Bye message': '¡Adiós {name}!',
|
||||
},
|
||||
},
|
||||
Timing: {
|
||||
createTimer: jest.genMockFunction(),
|
||||
deleteTimer: jest.genMockFunction(),
|
||||
},
|
||||
GraphPhotoUpload: {
|
||||
upload: jest.genMockFunction(),
|
||||
},
|
||||
FacebookSDK: {
|
||||
login: jest.genMockFunction(),
|
||||
logout: jest.genMockFunction(),
|
||||
queryGraphPath: jest.genMockFunction().mockImpl(
|
||||
(path, method, params, callback) => callback()
|
||||
),
|
||||
},
|
||||
DataManager: {
|
||||
queryData: jest.genMockFunction(),
|
||||
},
|
||||
UIManager: {
|
||||
customBubblingEventTypes: {},
|
||||
customDirectEventTypes: {},
|
||||
},
|
||||
AsyncLocalStorage: {
|
||||
getItem: jest.genMockFunction(),
|
||||
setItem: jest.genMockFunction(),
|
||||
removeItem: jest.genMockFunction(),
|
||||
clear: jest.genMockFunction(),
|
||||
},
|
||||
SourceCode: {
|
||||
scriptURL: null,
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = NativeModules;
|
@ -15,13 +15,12 @@ var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
var NativeModules = require('NativeModules');
|
||||
var PropTypes = require('ReactPropTypes');
|
||||
var React = require('React');
|
||||
var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
var View = require('View');
|
||||
|
||||
var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass');
|
||||
var keyMirror = require('keyMirror');
|
||||
var merge = require('merge');
|
||||
var requireNativeComponent = require('requireNativeComponent');
|
||||
var verifyPropTypes = require('verifyPropTypes');
|
||||
|
||||
var SpinnerSize = keyMirror({
|
||||
large: null,
|
||||
@ -100,14 +99,17 @@ var styles = StyleSheet.create({
|
||||
}
|
||||
});
|
||||
|
||||
var UIActivityIndicatorView = createReactIOSNativeComponentClass({
|
||||
validAttributes: merge(
|
||||
ReactIOSViewAttributes.UIView, {
|
||||
activityIndicatorViewStyle: true, // UIActivityIndicatorViewStyle=UIActivityIndicatorViewStyleWhite
|
||||
animating: true,
|
||||
color: true,
|
||||
}),
|
||||
uiViewClassName: 'UIActivityIndicatorView',
|
||||
});
|
||||
var UIActivityIndicatorView = requireNativeComponent(
|
||||
'UIActivityIndicatorView',
|
||||
null
|
||||
);
|
||||
if (__DEV__) {
|
||||
var nativeOnlyProps = {activityIndicatorViewStyle: true};
|
||||
verifyPropTypes(
|
||||
ActivityIndicatorIOS,
|
||||
UIActivityIndicatorView.viewConfig,
|
||||
nativeOnlyProps
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = ActivityIndicatorIOS;
|
||||
|
@ -16,14 +16,11 @@
|
||||
var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
var PropTypes = require('ReactPropTypes');
|
||||
var React = require('React');
|
||||
var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
|
||||
var RCTDatePickerIOSConsts = require('NativeModules').UIManager.RCTDatePicker.Constants;
|
||||
var StyleSheet = require('StyleSheet');
|
||||
var View = require('View');
|
||||
|
||||
var createReactIOSNativeComponentClass =
|
||||
require('createReactIOSNativeComponentClass');
|
||||
var merge = require('merge');
|
||||
var requireNativeComponent = require('requireNativeComponent');
|
||||
|
||||
var DATEPICKER = 'datepicker';
|
||||
|
||||
@ -148,18 +145,6 @@ var styles = StyleSheet.create({
|
||||
},
|
||||
});
|
||||
|
||||
var rkDatePickerIOSAttributes = merge(ReactIOSViewAttributes.UIView, {
|
||||
date: true,
|
||||
maximumDate: true,
|
||||
minimumDate: true,
|
||||
mode: true,
|
||||
minuteInterval: true,
|
||||
timeZoneOffsetInMinutes: true,
|
||||
});
|
||||
|
||||
var RCTDatePickerIOS = createReactIOSNativeComponentClass({
|
||||
validAttributes: rkDatePickerIOSAttributes,
|
||||
uiViewClassName: 'RCTDatePicker',
|
||||
});
|
||||
var RCTDatePickerIOS = requireNativeComponent('RCTDatePicker', DatePickerIOS);
|
||||
|
||||
module.exports = DatePickerIOS;
|
||||
|
@ -13,6 +13,7 @@
|
||||
|
||||
var EdgeInsetsPropType = require('EdgeInsetsPropType');
|
||||
var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
var Platform = require('Platform');
|
||||
var React = require('React');
|
||||
var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
|
||||
var View = require('View');
|
||||
@ -21,8 +22,15 @@ var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentC
|
||||
var deepDiffer = require('deepDiffer');
|
||||
var insetsDiffer = require('insetsDiffer');
|
||||
var merge = require('merge');
|
||||
var requireNativeComponent = require('requireNativeComponent');
|
||||
|
||||
type Event = Object;
|
||||
type MapRegion = {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
latitudeDelta: number;
|
||||
longitudeDelta: number;
|
||||
};
|
||||
|
||||
var MapView = React.createClass({
|
||||
mixins: [NativeMethodsMixin],
|
||||
@ -150,46 +158,30 @@ var MapView = React.createClass({
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<RCTMap
|
||||
style={this.props.style}
|
||||
showsUserLocation={this.props.showsUserLocation}
|
||||
zoomEnabled={this.props.zoomEnabled}
|
||||
rotateEnabled={this.props.rotateEnabled}
|
||||
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}
|
||||
onChange={this._onChange}
|
||||
onTouchStart={this.props.onTouchStart}
|
||||
onTouchMove={this.props.onTouchMove}
|
||||
onTouchEnd={this.props.onTouchEnd}
|
||||
onTouchCancel={this.props.onTouchCancel}
|
||||
/>
|
||||
);
|
||||
return <RCTMap {...this.props} onChange={this._onChange} />;
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
var RCTMap = createReactIOSNativeComponentClass({
|
||||
validAttributes: merge(
|
||||
ReactIOSViewAttributes.UIView, {
|
||||
showsUserLocation: true,
|
||||
zoomEnabled: true,
|
||||
rotateEnabled: true,
|
||||
pitchEnabled: true,
|
||||
scrollEnabled: true,
|
||||
region: {diff: deepDiffer},
|
||||
annotations: {diff: deepDiffer},
|
||||
maxDelta: true,
|
||||
minDelta: true,
|
||||
legalLabelInsets: {diff: insetsDiffer},
|
||||
}
|
||||
),
|
||||
uiViewClassName: 'RCTMap',
|
||||
});
|
||||
if (Platform.OS === 'android') {
|
||||
var RCTMap = createReactIOSNativeComponentClass({
|
||||
validAttributes: merge(
|
||||
ReactIOSViewAttributes.UIView, {
|
||||
showsUserLocation: true,
|
||||
zoomEnabled: true,
|
||||
rotateEnabled: true,
|
||||
pitchEnabled: true,
|
||||
scrollEnabled: true,
|
||||
region: {diff: deepDiffer},
|
||||
annotations: {diff: deepDiffer},
|
||||
maxDelta: true,
|
||||
minDelta: true,
|
||||
legalLabelInsets: {diff: insetsDiffer},
|
||||
}
|
||||
),
|
||||
uiViewClassName: 'RCTMap',
|
||||
});
|
||||
} else {
|
||||
var RCTMap = requireNativeComponent('RCTMap', MapView);
|
||||
}
|
||||
|
||||
module.exports = MapView;
|
||||
|
@ -252,6 +252,16 @@ var NavigatorIOS = React.createClass({
|
||||
*/
|
||||
tintColor: PropTypes.string,
|
||||
|
||||
/**
|
||||
* The background color of the navigation bar
|
||||
*/
|
||||
barTintColor: PropTypes.string,
|
||||
|
||||
/**
|
||||
* The text color of the navigation bar title
|
||||
*/
|
||||
titleTextColor: PropTypes.string,
|
||||
|
||||
},
|
||||
|
||||
navigator: (undefined: ?Object),
|
||||
@ -554,7 +564,9 @@ var NavigatorIOS = React.createClass({
|
||||
rightButtonTitle={route.rightButtonTitle}
|
||||
onNavRightButtonTap={route.onRightButtonPress}
|
||||
navigationBarHidden={this.props.navigationBarHidden}
|
||||
tintColor={this.props.tintColor}>
|
||||
tintColor={this.props.tintColor}
|
||||
barTintColor={this.props.barTintColor}
|
||||
titleTextColor={this.props.titleTextColor}>
|
||||
<Component
|
||||
navigator={this.navigator}
|
||||
route={route}
|
||||
|
@ -371,10 +371,6 @@ if (Platform.OS === 'android') {
|
||||
uiViewClassName: 'AndroidHorizontalScrollView',
|
||||
});
|
||||
} else if (Platform.OS === 'ios') {
|
||||
var RCTScrollView = createReactIOSNativeComponentClass({
|
||||
validAttributes: validAttributes,
|
||||
uiViewClassName: 'RCTScrollView',
|
||||
});
|
||||
var RCTScrollView = requireNativeComponent('RCTScrollView', ScrollView);
|
||||
}
|
||||
|
||||
|
@ -12,16 +12,11 @@
|
||||
'use strict';
|
||||
|
||||
var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
var Platform = require('Platform');
|
||||
var PropTypes = require('ReactPropTypes');
|
||||
var React = require('React');
|
||||
var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
var View = require('View');
|
||||
|
||||
var createReactIOSNativeComponentClass =
|
||||
require('createReactIOSNativeComponentClass');
|
||||
var merge = require('merge');
|
||||
var requireNativeComponent = require('requireNativeComponent');
|
||||
|
||||
type Event = Object;
|
||||
@ -56,6 +51,18 @@ var SliderIOS = React.createClass({
|
||||
*/
|
||||
maximumValue: PropTypes.number,
|
||||
|
||||
/**
|
||||
* The color used for the track to the left of the button. Overrides the
|
||||
* default blue gradient image.
|
||||
*/
|
||||
minimumTrackTintColor: PropTypes.string,
|
||||
|
||||
/**
|
||||
* The color used for the track to the right of the button. Overrides the
|
||||
* default blue gradient image.
|
||||
*/
|
||||
maximumTrackTintColor: PropTypes.string,
|
||||
|
||||
/**
|
||||
* Callback continuously called while the user is dragging the slider.
|
||||
*/
|
||||
@ -86,6 +93,8 @@ var SliderIOS = React.createClass({
|
||||
value={this.props.value}
|
||||
maximumValue={this.props.maximumValue}
|
||||
minimumValue={this.props.minimumValue}
|
||||
minimumTrackTintColor={this.props.minimumTrackTintColor}
|
||||
maximumTrackTintColor={this.props.maximumTrackTintColor}
|
||||
onChange={this._onValueChange}
|
||||
/>
|
||||
);
|
||||
@ -98,20 +107,6 @@ var styles = StyleSheet.create({
|
||||
},
|
||||
});
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
var RCTSlider = requireNativeComponent('RCTSlider', SliderIOS);
|
||||
} else {
|
||||
var validAttributes = {
|
||||
...ReactIOSViewAttributes.UIView,
|
||||
value: true,
|
||||
minimumValue: true,
|
||||
maximumValue: true,
|
||||
};
|
||||
|
||||
var RCTSlider = createReactIOSNativeComponentClass({
|
||||
validAttributes: validAttributes,
|
||||
uiViewClassName: 'RCTSlider',
|
||||
});
|
||||
}
|
||||
var RCTSlider = requireNativeComponent('RCTSlider', SliderIOS);
|
||||
|
||||
module.exports = SliderIOS;
|
||||
|
@ -16,11 +16,9 @@
|
||||
var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
var PropTypes = require('ReactPropTypes');
|
||||
var React = require('React');
|
||||
var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
|
||||
var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass');
|
||||
var merge = require('merge');
|
||||
var requireNativeComponent = require('requireNativeComponent');
|
||||
|
||||
var SWITCH = 'switch';
|
||||
|
||||
@ -88,20 +86,16 @@ var SwitchIOS = React.createClass({
|
||||
|
||||
// The underlying switch might have changed, but we're controlled,
|
||||
// and so want to ensure it represents our value.
|
||||
this.refs[SWITCH].setNativeProps({on: this.props.value});
|
||||
this.refs[SWITCH].setNativeProps({value: this.props.value});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<RCTSwitch
|
||||
{...this.props}
|
||||
ref={SWITCH}
|
||||
style={[styles.rkSwitch, this.props.style]}
|
||||
enabled={!this.props.disabled}
|
||||
on={this.props.value}
|
||||
onChange={this._onChange}
|
||||
onTintColor={this.props.onTintColor}
|
||||
thumbTintColor={this.props.thumbTintColor}
|
||||
tintColor={this.props.tintColor}
|
||||
style={[styles.rkSwitch, this.props.style]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -114,17 +108,6 @@ var styles = StyleSheet.create({
|
||||
},
|
||||
});
|
||||
|
||||
var rkSwitchAttributes = merge(ReactIOSViewAttributes.UIView, {
|
||||
onTintColor: true,
|
||||
tintColor: true,
|
||||
thumbTintColor: true,
|
||||
on: true,
|
||||
enabled: true,
|
||||
});
|
||||
|
||||
var RCTSwitch = createReactIOSNativeComponentClass({
|
||||
validAttributes: rkSwitchAttributes,
|
||||
uiViewClassName: 'RCTSwitch',
|
||||
});
|
||||
var RCTSwitch = requireNativeComponent('RCTSwitch', SwitchIOS);
|
||||
|
||||
module.exports = SwitchIOS;
|
||||
|
@ -12,12 +12,11 @@
|
||||
'use strict';
|
||||
|
||||
var React = require('React');
|
||||
var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
var TabBarItemIOS = require('TabBarItemIOS');
|
||||
var View = require('View');
|
||||
|
||||
var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass');
|
||||
var requireNativeComponent = require('requireNativeComponent');
|
||||
|
||||
var TabBarIOS = React.createClass({
|
||||
statics: {
|
||||
@ -43,10 +42,6 @@ var styles = StyleSheet.create({
|
||||
}
|
||||
});
|
||||
|
||||
var config = {
|
||||
validAttributes: ReactIOSViewAttributes.UIView,
|
||||
uiViewClassName: 'RCTTabBar',
|
||||
};
|
||||
var RCTTabBar = createReactIOSNativeComponentClass(config);
|
||||
var RCTTabBar = requireNativeComponent('RCTTabBar', TabBarIOS);
|
||||
|
||||
module.exports = TabBarIOS;
|
||||
|
@ -13,13 +13,11 @@
|
||||
|
||||
var Image = require('Image');
|
||||
var React = require('React');
|
||||
var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
|
||||
var StaticContainer = require('StaticContainer.react');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
var View = require('View');
|
||||
|
||||
var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass');
|
||||
var merge = require('merge');
|
||||
var requireNativeComponent = require('requireNativeComponent');
|
||||
|
||||
var TabBarItemIOS = React.createClass({
|
||||
propTypes: {
|
||||
@ -121,7 +119,7 @@ var TabBarItemIOS = React.createClass({
|
||||
selectedIcon={this.props.selectedIcon && this.props.selectedIcon.uri}
|
||||
onPress={this.props.onPress}
|
||||
selected={this.props.selected}
|
||||
badgeValue={badge}
|
||||
badge={badge}
|
||||
title={this.props.title}
|
||||
style={[styles.tab, this.props.style]}>
|
||||
{tabContents}
|
||||
@ -140,15 +138,6 @@ var styles = StyleSheet.create({
|
||||
}
|
||||
});
|
||||
|
||||
var RCTTabBarItem = createReactIOSNativeComponentClass({
|
||||
validAttributes: merge(ReactIOSViewAttributes.UIView, {
|
||||
title: true,
|
||||
icon: true,
|
||||
selectedIcon: true,
|
||||
selected: true,
|
||||
badgeValue: true,
|
||||
}),
|
||||
uiViewClassName: 'RCTTabBarItem',
|
||||
});
|
||||
var RCTTabBarItem = requireNativeComponent('RCTTabBarItem', TabBarItemIOS);
|
||||
|
||||
module.exports = TabBarItemIOS;
|
||||
|
@ -350,6 +350,9 @@ var TextInput = React.createClass({
|
||||
|
||||
componentWillUnmount: function() {
|
||||
this._focusSubscription && this._focusSubscription.remove();
|
||||
if (this.isFocused()) {
|
||||
this.blur();
|
||||
}
|
||||
},
|
||||
|
||||
_bufferTimeout: (undefined: ?number),
|
||||
|
@ -13,12 +13,13 @@
|
||||
|
||||
var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
var PropTypes = require('ReactPropTypes');
|
||||
var RCTUIManager = require('NativeModules').UIManager;
|
||||
var React = require('React');
|
||||
var ReactIOSStyleAttributes = require('ReactIOSStyleAttributes');
|
||||
var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
|
||||
var StyleSheetPropType = require('StyleSheetPropType');
|
||||
var ViewStylePropTypes = require('ViewStylePropTypes');
|
||||
|
||||
|
||||
var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass');
|
||||
|
||||
var stylePropType = StyleSheetPropType(ViewStylePropTypes);
|
||||
@ -136,6 +137,20 @@ var View = React.createClass({
|
||||
* (or one of its superviews).
|
||||
*/
|
||||
removeClippedSubviews: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* Whether this view should render itself (and all of its children) into a
|
||||
* single hardware texture on the GPU.
|
||||
*
|
||||
* On Android, this is useful for animations and interactions that only
|
||||
* modify opacity, rotation, translation, and/or scale: in those cases, the
|
||||
* view doesn't have to be redrawn and display lists don't need to be
|
||||
* re-executed. The texture can just be re-used and re-composited with
|
||||
* different parameters. The downside is that this can use up limited video
|
||||
* memory, so this prop should be set back to false at the end of the
|
||||
* interaction/animation.
|
||||
*/
|
||||
renderToHardwareTextureAndroid: PropTypes.bool,
|
||||
},
|
||||
|
||||
render: function() {
|
||||
@ -143,17 +158,26 @@ var View = React.createClass({
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
var RCTView = createReactIOSNativeComponentClass({
|
||||
validAttributes: ReactIOSViewAttributes.RCTView,
|
||||
uiViewClassName: 'RCTView',
|
||||
});
|
||||
RCTView.propTypes = View.propTypes;
|
||||
if (__DEV__) {
|
||||
var viewConfig = RCTUIManager.viewConfigs && RCTUIManager.viewConfigs.RCTView || {};
|
||||
for (var prop in viewConfig.nativeProps) {
|
||||
var viewAny: any = View; // Appease flow
|
||||
if (!viewAny.propTypes[prop] && !ReactIOSStyleAttributes[prop]) {
|
||||
throw new Error(
|
||||
'View is missing propType for native prop `' + prop + '`'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var ViewToExport = RCTView;
|
||||
if (__DEV__) {
|
||||
ViewToExport = View;
|
||||
}
|
||||
|
||||
|
||||
module.exports = ViewToExport;
|
||||
|
@ -34,7 +34,10 @@ var ViewStylePropTypes = {
|
||||
),
|
||||
shadowOpacity: ReactPropTypes.number,
|
||||
shadowRadius: ReactPropTypes.number,
|
||||
transform: ReactPropTypes.arrayOf(ReactPropTypes.object),
|
||||
transformMatrix: ReactPropTypes.arrayOf(ReactPropTypes.number),
|
||||
|
||||
// DEPRECATED
|
||||
rotation: ReactPropTypes.number,
|
||||
scaleX: ReactPropTypes.number,
|
||||
scaleY: ReactPropTypes.number,
|
||||
|
@ -19,16 +19,13 @@ var StyleSheet = require('StyleSheet');
|
||||
var Text = require('Text');
|
||||
var View = require('View');
|
||||
|
||||
var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass');
|
||||
var invariant = require('invariant');
|
||||
var keyMirror = require('keyMirror');
|
||||
var insetsDiffer = require('insetsDiffer');
|
||||
var merge = require('merge');
|
||||
var requireNativeComponent = require('requireNativeComponent');
|
||||
|
||||
var PropTypes = React.PropTypes;
|
||||
var RCTWebViewManager = require('NativeModules').WebViewManager;
|
||||
|
||||
var invariant = require('invariant');
|
||||
|
||||
var BGWASH = 'rgba(255,255,255,0.8)';
|
||||
var RCT_WEBVIEW_REF = 'webview';
|
||||
|
||||
@ -87,6 +84,8 @@ var WebView = React.createClass({
|
||||
html: PropTypes.string,
|
||||
renderError: PropTypes.func, // view to show if there's an error
|
||||
renderLoading: PropTypes.func, // loading indicator to show
|
||||
bounces: PropTypes.bool,
|
||||
scrollEnabled: PropTypes.bool,
|
||||
automaticallyAdjustContentInsets: PropTypes.bool,
|
||||
shouldInjectAJAXHandler: PropTypes.bool,
|
||||
contentInset: EdgeInsetsPropType,
|
||||
@ -131,7 +130,7 @@ var WebView = React.createClass({
|
||||
);
|
||||
}
|
||||
|
||||
var webViewStyles = [styles.container, this.props.style];
|
||||
var webViewStyles = [styles.container, styles.webView, this.props.style];
|
||||
if (this.state.viewState === WebViewState.LOADING ||
|
||||
this.state.viewState === WebViewState.ERROR) {
|
||||
// if we're in either LOADING or ERROR states, don't show the webView
|
||||
@ -145,6 +144,8 @@ var WebView = React.createClass({
|
||||
style={webViewStyles}
|
||||
url={this.props.url}
|
||||
html={this.props.html}
|
||||
bounces={this.props.bounces}
|
||||
scrollEnabled={this.props.scrollEnabled}
|
||||
shouldInjectAJAXHandler={this.props.shouldInjectAJAXHandler}
|
||||
contentInset={this.props.contentInset}
|
||||
automaticallyAdjustContentInsets={this.props.automaticallyAdjustContentInsets}
|
||||
@ -209,16 +210,7 @@ var WebView = React.createClass({
|
||||
},
|
||||
});
|
||||
|
||||
var RCTWebView = createReactIOSNativeComponentClass({
|
||||
validAttributes: merge(ReactIOSViewAttributes.UIView, {
|
||||
url: true,
|
||||
html: true,
|
||||
contentInset: {diff: insetsDiffer},
|
||||
automaticallyAdjustContentInsets: true,
|
||||
shouldInjectAJAXHandler: true
|
||||
}),
|
||||
uiViewClassName: 'RCTWebView',
|
||||
});
|
||||
var RCTWebView = requireNativeComponent('RCTWebView', WebView);
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
container: {
|
||||
@ -250,6 +242,9 @@ var styles = StyleSheet.create({
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
webView: {
|
||||
backgroundColor: '#ffffff',
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = WebView;
|
||||
|
@ -135,11 +135,12 @@ var GESTURE_ACTIONS = [
|
||||
* />
|
||||
* ```
|
||||
*
|
||||
* ### Navigation Methods
|
||||
* ### Navigator Methods
|
||||
*
|
||||
* `Navigator` can be told to navigate in two ways. If you have a ref to
|
||||
* the element, you can invoke several methods on it to trigger navigation:
|
||||
* If you have a ref to the Navigator element, you can invoke several methods
|
||||
* on it to trigger navigation:
|
||||
*
|
||||
* - `getCurrentRoutes()` - returns the current list of routes
|
||||
* - `jumpBack()` - Jump backward without unmounting the current scene
|
||||
* - `jumpForward()` - Jump forward to the next scene in the route stack
|
||||
* - `jumpTo(route)` - Transition to an existing scene without unmounting
|
||||
@ -156,18 +157,39 @@ var GESTURE_ACTIONS = [
|
||||
* - `popToTop()` - Pop to the first scene in the stack, unmounting every
|
||||
* other scene
|
||||
*
|
||||
* ### Navigator Object
|
||||
* ### Navigation Context
|
||||
*
|
||||
* The navigator object is made available to scenes through the `renderScene`
|
||||
* function. The object has all of the navigation methods on it, as well as a
|
||||
* few utilities:
|
||||
* The navigator context object is made available to scenes through the
|
||||
* `renderScene` function. Alternatively, any scene or component inside a
|
||||
* Navigator can get the navigation context by calling
|
||||
* `Navigator.getContext(this)`.
|
||||
*
|
||||
* - `parentNavigator` - a refrence to the parent navigator object that was
|
||||
* passed in through props.navigator
|
||||
* - `onWillFocus` - used to pass a navigation focus event up to the parent
|
||||
* navigator
|
||||
* - `onDidFocus` - used to pass a navigation focus event up to the parent
|
||||
* navigator
|
||||
* Unlike the Navigator methods, the functions in navigation context do not
|
||||
* directly control a specific navigator. Instead, the navigator context allows
|
||||
* a scene to request navigation from its parents. Navigation requests will
|
||||
* travel up through the hierarchy of Navigators, and will be resolved by the
|
||||
* deepest active navigator.
|
||||
*
|
||||
* Navigation context objects contain the following:
|
||||
*
|
||||
* - `getCurrentRoutes()` - returns the routes for the closest navigator
|
||||
* - `jumpBack()` - Jump backward without unmounting the current scene
|
||||
* - `jumpForward()` - Jump forward to the next scene in the route stack
|
||||
* - `jumpTo(route)` - Transition to an existing scene without unmounting
|
||||
* - `parentNavigator` - a refrence to the parent navigation context
|
||||
* - `push(route)` - Navigate forward to a new scene, squashing any scenes
|
||||
* that you could `jumpForward` to
|
||||
* - `pop()` - Transition back and unmount the current scene
|
||||
* - `replace(route)` - Replace the current scene with a new route
|
||||
* - `replaceAtIndex(route, index)` - Replace a scene as specified by an index
|
||||
* - `replacePrevious(route)` - Replace the previous scene
|
||||
* - `route` - The route that was used to render the scene with this context
|
||||
* - `immediatelyResetRouteStack(routeStack)` - Reset every scene with an
|
||||
* array of routes
|
||||
* - `popToRoute(route)` - Pop to a particular scene, as specified by it's
|
||||
* route. All scenes after it will be unmounted
|
||||
* - `popToTop()` - Pop to the first scene in the stack, unmounting every
|
||||
* other scene
|
||||
*
|
||||
*/
|
||||
var Navigator = React.createClass({
|
||||
@ -306,25 +328,30 @@ var Navigator = React.createClass({
|
||||
this.parentNavigator = getNavigatorContext(this) || this.props.navigator;
|
||||
this._subRouteFocus = [];
|
||||
this.navigatorContext = {
|
||||
// Actions for child navigators or interceptors:
|
||||
setHandlerForRoute: this.setHandlerForRoute,
|
||||
request: this.request,
|
||||
|
||||
// Contextual utilities
|
||||
parentNavigator: this.parentNavigator,
|
||||
getCurrentRoutes: this.getCurrentRoutes,
|
||||
// `route` is injected by NavigatorStaticContextContainer
|
||||
|
||||
// Legacy, imperitive nav actions. Use request when possible.
|
||||
// Contextual nav actions
|
||||
pop: this.requestPop,
|
||||
popToRoute: this.requestPopTo,
|
||||
|
||||
// Legacy, imperitive nav actions. Will transition these to contextual actions
|
||||
jumpBack: this.jumpBack,
|
||||
jumpForward: this.jumpForward,
|
||||
jumpTo: this.jumpTo,
|
||||
push: this.push,
|
||||
pop: this.pop,
|
||||
replace: this.replace,
|
||||
replaceAtIndex: this.replaceAtIndex,
|
||||
replacePrevious: this.replacePrevious,
|
||||
replacePreviousAndPop: this.replacePreviousAndPop,
|
||||
immediatelyResetRouteStack: this.immediatelyResetRouteStack,
|
||||
resetTo: this.resetTo,
|
||||
popToRoute: this.popToRoute,
|
||||
popToTop: this.popToTop,
|
||||
};
|
||||
this._handlers = {};
|
||||
@ -349,6 +376,14 @@ var Navigator = React.createClass({
|
||||
return this._handleRequest.apply(null, arguments);
|
||||
},
|
||||
|
||||
requestPop: function() {
|
||||
return this.request('pop');
|
||||
},
|
||||
|
||||
requestPopTo: function(route) {
|
||||
return this.request('pop', route);
|
||||
},
|
||||
|
||||
_handleRequest: function(action, arg1, arg2) {
|
||||
var childHandler = this._handlers[this.state.presentedIndex];
|
||||
if (childHandler && childHandler(action, arg1, arg2)) {
|
||||
@ -356,7 +391,7 @@ var Navigator = React.createClass({
|
||||
}
|
||||
switch (action) {
|
||||
case 'pop':
|
||||
return this._handlePop();
|
||||
return this._handlePop(arg1);
|
||||
case 'push':
|
||||
return this._handlePush(arg1);
|
||||
default:
|
||||
@ -365,11 +400,20 @@ var Navigator = React.createClass({
|
||||
}
|
||||
},
|
||||
|
||||
_handlePop: function() {
|
||||
_handlePop: function(route) {
|
||||
if (route) {
|
||||
var hasRoute = this.state.routeStack.indexOf(route) !== -1;
|
||||
if (hasRoute) {
|
||||
this.popToRoute(route);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (this.state.presentedIndex === 0) {
|
||||
return false;
|
||||
}
|
||||
this._popN(1);
|
||||
this.pop();
|
||||
return true;
|
||||
},
|
||||
|
||||
@ -415,7 +459,7 @@ var Navigator = React.createClass({
|
||||
},
|
||||
|
||||
_handleAndroidBackPress: function() {
|
||||
var didPop = this.pop();
|
||||
var didPop = this.requestPop();
|
||||
if (!didPop) {
|
||||
BackAndroid.exitApp();
|
||||
}
|
||||
@ -494,6 +538,7 @@ var Navigator = React.createClass({
|
||||
|
||||
_completeTransition: function() {
|
||||
if (this.spring.getCurrentValue() === 1) {
|
||||
this._onAnimationEnd();
|
||||
var presentedIndex = this.state.toIndex;
|
||||
this.state.presentedIndex = presentedIndex;
|
||||
this.state.fromIndex = presentedIndex;
|
||||
@ -515,6 +560,7 @@ var Navigator = React.createClass({
|
||||
// For visual consistency, the from index is always used to configure the spring
|
||||
this.state.sceneConfigStack[this.state.fromIndex]
|
||||
);
|
||||
this._onAnimationStart();
|
||||
this.state.isAnimating = true;
|
||||
this.spring.setVelocity(v);
|
||||
this.spring.setEndValue(1);
|
||||
@ -573,6 +619,34 @@ var Navigator = React.createClass({
|
||||
}
|
||||
},
|
||||
|
||||
_onAnimationStart: function() {
|
||||
this._setRenderSceneToHarwareTextureAndroid(this.state.fromIndex, true);
|
||||
this._setRenderSceneToHarwareTextureAndroid(this.state.toIndex, true);
|
||||
|
||||
var navBar = this._navBar;
|
||||
if (navBar && navBar.onAnimationStart) {
|
||||
navBar.onAnimationStart(this.state.fromIndex, this.state.toIndex);
|
||||
}
|
||||
},
|
||||
|
||||
_onAnimationEnd: function() {
|
||||
this._setRenderSceneToHarwareTextureAndroid(this.state.fromIndex, false);
|
||||
this._setRenderSceneToHarwareTextureAndroid(this.state.toIndex, false);
|
||||
|
||||
var navBar = this._navBar;
|
||||
if (navBar && navBar.onAnimationEnd) {
|
||||
navBar.onAnimationEnd(this.state.fromIndex, this.state.toIndex);
|
||||
}
|
||||
},
|
||||
|
||||
_setRenderSceneToHarwareTextureAndroid: function(sceneIndex, shouldRenderToHardwareTexture) {
|
||||
var viewAtIndex = this.refs['scene_' + sceneIndex];
|
||||
if (viewAtIndex === null || viewAtIndex === undefined) {
|
||||
return;
|
||||
}
|
||||
viewAtIndex.setNativeProps({renderToHardwareTextureAndroid: shouldRenderToHardwareTexture});
|
||||
},
|
||||
|
||||
/**
|
||||
* Becomes the responder on touch start (capture) while animating so that it
|
||||
* blocks all touch interactions inside of it. However, this responder lock
|
||||
@ -610,6 +684,7 @@ var Navigator = React.createClass({
|
||||
this.state.fromIndex = this.state.presentedIndex;
|
||||
var gestureSceneDelta = this._deltaForGestureAction(this._activeGestureAction);
|
||||
this.state.toIndex = this.state.presentedIndex + gestureSceneDelta;
|
||||
this._onAnimationStart();
|
||||
}
|
||||
},
|
||||
|
||||
@ -873,7 +948,7 @@ var Navigator = React.createClass({
|
||||
},
|
||||
|
||||
pop: function() {
|
||||
return this.request('pop');
|
||||
this._popN(1);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1004,8 +1079,7 @@ var Navigator = React.createClass({
|
||||
// To avoid visual glitches, we never re-render scenes during a transition.
|
||||
// We assume that `state.updatingRangeLength` will have a length during the
|
||||
// initial render of any scene
|
||||
var shouldRenderScenes = !this.state.isAnimating &&
|
||||
this.state.updatingRangeLength !== 0;
|
||||
var shouldRenderScenes = this.state.updatingRangeLength !== 0;
|
||||
if (shouldRenderScenes) {
|
||||
return (
|
||||
<StaticContainer shouldUpdate={true}>
|
||||
|
@ -138,6 +138,37 @@ var NavigatorBreadcrumbNavigationBar = React.createClass({
|
||||
}
|
||||
},
|
||||
|
||||
onAnimationStart: function(fromIndex, toIndex) {
|
||||
var max = Math.max(fromIndex, toIndex);
|
||||
var min = Math.min(fromIndex, toIndex);
|
||||
for (var index = min; index <= max; index++) {
|
||||
this._setRenderViewsToHardwareTextureAndroid(index, true);
|
||||
}
|
||||
},
|
||||
|
||||
onAnimationEnd: function(fromIndex, toIndex) {
|
||||
var max = Math.max(fromIndex, toIndex);
|
||||
var min = Math.min(fromIndex, toIndex);
|
||||
for (var index = min; index <= max; index++) {
|
||||
this._setRenderViewsToHardwareTextureAndroid(index, false);
|
||||
}
|
||||
},
|
||||
|
||||
_setRenderViewsToHardwareTextureAndroid: function(index, renderToHardwareTexture) {
|
||||
var props = {
|
||||
renderToHardwareTextureAndroid: renderToHardwareTexture,
|
||||
};
|
||||
|
||||
this.refs['crumb_' + index].setNativeProps(props);
|
||||
this.refs['icon_' + index].setNativeProps(props);
|
||||
this.refs['separator_' + index].setNativeProps(props);
|
||||
this.refs['title_' + index].setNativeProps(props);
|
||||
var right = this.refs['right_' + index];
|
||||
if (right) {
|
||||
right.setNativeProps(props);
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var navState = this.props.navState;
|
||||
var icons = navState && navState.routeStack.map(this._renderOrReturnBreadcrumb);
|
||||
@ -260,7 +291,7 @@ var styles = StyleSheet.create({
|
||||
height: NavigatorNavigationBarStyles.General.TotalNavHeight,
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: NavigatorNavigationBarStyles.General.ScreenWidth,
|
||||
right: 0,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -26,12 +26,13 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var Dimensions = require('Dimensions');
|
||||
var NavigatorNavigationBarStyles = require('NavigatorNavigationBarStyles');
|
||||
|
||||
var buildStyleInterpolator = require('buildStyleInterpolator');
|
||||
var merge = require('merge');
|
||||
|
||||
var SCREEN_WIDTH = NavigatorNavigationBarStyles.General.ScreenWidth;
|
||||
var SCREEN_WIDTH = Dimensions.get('window').width;
|
||||
var STATUS_BAR_HEIGHT = NavigatorNavigationBarStyles.General.StatusBarHeight;
|
||||
var NAV_BAR_HEIGHT = NavigatorNavigationBarStyles.General.NavBarHeight;
|
||||
|
||||
@ -39,7 +40,6 @@ var SPACING = 4;
|
||||
var ICON_WIDTH = 40;
|
||||
var SEPARATOR_WIDTH = 9;
|
||||
var CRUMB_WIDTH = ICON_WIDTH + SEPARATOR_WIDTH;
|
||||
var RIGHT_BUTTON_WIDTH = 58;
|
||||
|
||||
var OPACITY_RATIO = 100;
|
||||
var ICON_INACTIVE_OPACITY = 0.6;
|
||||
@ -74,18 +74,17 @@ var TITLE_BASE = {
|
||||
// For first title styles, make sure first title is centered
|
||||
var FIRST_TITLE_BASE = merge(TITLE_BASE, {
|
||||
left: 0,
|
||||
right: 0,
|
||||
alignItems: 'center',
|
||||
width: SCREEN_WIDTH,
|
||||
height: NAV_BAR_HEIGHT,
|
||||
});
|
||||
|
||||
var RIGHT_BUTTON_BASE = {
|
||||
position: 'absolute',
|
||||
top: STATUS_BAR_HEIGHT,
|
||||
left: SCREEN_WIDTH - SPACING - RIGHT_BUTTON_WIDTH,
|
||||
right: SPACING,
|
||||
overflow: 'hidden',
|
||||
opacity: 1,
|
||||
width: RIGHT_BUTTON_WIDTH,
|
||||
height: NAV_BAR_HEIGHT,
|
||||
backgroundColor: 'transparent',
|
||||
};
|
||||
|
@ -192,7 +192,7 @@ var styles = StyleSheet.create({
|
||||
height: NavigatorNavigationBarStyles.General.TotalNavHeight,
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: NavigatorNavigationBarStyles.General.ScreenWidth,
|
||||
right: 0,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
});
|
||||
|
@ -169,7 +169,6 @@ module.exports = {
|
||||
NavBarHeight: NAV_BAR_HEIGHT,
|
||||
StatusBarHeight: STATUS_BAR_HEIGHT,
|
||||
TotalNavHeight: NAV_HEIGHT,
|
||||
ScreenWidth: SCREEN_WIDTH,
|
||||
},
|
||||
Interpolators,
|
||||
Stages,
|
||||
|
@ -198,6 +198,7 @@
|
||||
58B511F01A9E6C8500147676 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_STATIC_ANALYZER_MODE = deep;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
||||
@ -206,6 +207,7 @@
|
||||
LIBRARY_SEARCH_PATHS = "$(inherited)";
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = RCTGeolocation;
|
||||
RUN_CLANG_STATIC_ANALYZER = YES;
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Debug;
|
||||
@ -213,6 +215,7 @@
|
||||
58B511F11A9E6C8500147676 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_STATIC_ANALYZER_MODE = deep;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
||||
|
@ -33,7 +33,9 @@ typedef struct {
|
||||
CLLocationAccuracy accuracy;
|
||||
} RCTLocationOptions;
|
||||
|
||||
static RCTLocationOptions RCTLocationOptionsWithJSON(id json)
|
||||
@implementation RCTConvert (RCTLocationOptions)
|
||||
|
||||
+ (RCTLocationOptions)RCTLocationOptions:(id)json
|
||||
{
|
||||
NSDictionary *options = [RCTConvert NSDictionary:json];
|
||||
return (RCTLocationOptions){
|
||||
@ -43,6 +45,8 @@ static RCTLocationOptions RCTLocationOptionsWithJSON(id json)
|
||||
};
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static NSDictionary *RCTPositionError(RCTPositionErrorCode code, NSString *msg /* nil for default */)
|
||||
{
|
||||
if (!msg) {
|
||||
@ -121,6 +125,12 @@ RCT_EXPORT_MODULE()
|
||||
- (void)dealloc
|
||||
{
|
||||
[_locationManager stopUpdatingLocation];
|
||||
_locationManager.delegate = nil;
|
||||
}
|
||||
|
||||
- (dispatch_queue_t)methodQueue
|
||||
{
|
||||
return dispatch_get_main_queue();
|
||||
}
|
||||
|
||||
#pragma mark - Private API
|
||||
@ -157,34 +167,26 @@ RCT_EXPORT_METHOD(startObserving:(NSDictionary *)optionsJSON)
|
||||
{
|
||||
[self checkLocationConfig];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
// Select best options
|
||||
_observerOptions = [RCTConvert RCTLocationOptions:optionsJSON];
|
||||
for (RCTLocationRequest *request in _pendingRequests) {
|
||||
_observerOptions.accuracy = MIN(_observerOptions.accuracy, request.options.accuracy);
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
});
|
||||
_locationManager.desiredAccuracy = _observerOptions.accuracy;
|
||||
[self beginLocationUpdates];
|
||||
_observingLocation = YES;
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(stopObserving)
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
// Stop observing
|
||||
_observingLocation = NO;
|
||||
|
||||
// Stop observing
|
||||
_observingLocation = NO;
|
||||
|
||||
// Stop updating if no pending requests
|
||||
if (_pendingRequests.count == 0) {
|
||||
[_locationManager stopUpdatingLocation];
|
||||
}
|
||||
|
||||
});
|
||||
// Stop updating if no pending requests
|
||||
if (_pendingRequests.count == 0) {
|
||||
[_locationManager stopUpdatingLocation];
|
||||
}
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(getCurrentPosition:(NSDictionary *)optionsJSON
|
||||
@ -198,56 +200,50 @@ RCT_EXPORT_METHOD(getCurrentPosition:(NSDictionary *)optionsJSON
|
||||
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]);
|
||||
if (![CLLocationManager locationServicesEnabled]) {
|
||||
if (errorBlock) {
|
||||
errorBlock(@[
|
||||
RCTPositionError(RCTPositionErrorUnavailable, @"Location services disabled.")
|
||||
]);
|
||||
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];
|
||||
if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied) {
|
||||
if (errorBlock) {
|
||||
errorBlock(@[
|
||||
RCTPositionError(RCTPositionErrorDenied, nil)
|
||||
]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Configure location manager and begin updating location
|
||||
_locationManager.desiredAccuracy = MIN(_locationManager.desiredAccuracy, options.accuracy);
|
||||
[self beginLocationUpdates];
|
||||
// Check if previous recorded location exists and is good enough
|
||||
RCTLocationOptions options = [RCTConvert RCTLocationOptions:optionsJSON];
|
||||
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
|
||||
|
@ -12,25 +12,23 @@
|
||||
'use strict';
|
||||
|
||||
var EdgeInsetsPropType = require('EdgeInsetsPropType');
|
||||
var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
var NativeModules = require('NativeModules');
|
||||
var Platform = require('Platform');
|
||||
var PropTypes = require('ReactPropTypes');
|
||||
var ImageResizeMode = require('ImageResizeMode');
|
||||
var ImageStylePropTypes = require('ImageStylePropTypes');
|
||||
var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
var NativeModules = require('NativeModules');
|
||||
var PropTypes = require('ReactPropTypes');
|
||||
var React = require('React');
|
||||
var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
var StyleSheetPropType = require('StyleSheetPropType');
|
||||
|
||||
var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass');
|
||||
var flattenStyle = require('flattenStyle');
|
||||
var insetsDiffer = require('insetsDiffer');
|
||||
var invariant = require('invariant');
|
||||
var merge = require('merge');
|
||||
var requireNativeComponent = require('requireNativeComponent');
|
||||
var warning = require('warning');
|
||||
var resolveAssetSource = require('resolveAssetSource');
|
||||
var verifyPropTypes = require('verifyPropTypes');
|
||||
var warning = require('warning');
|
||||
|
||||
/**
|
||||
* A react component for displaying different types of images,
|
||||
@ -125,10 +123,13 @@ var Image = React.createClass({
|
||||
'not be set directly on Image.');
|
||||
}
|
||||
}
|
||||
var style = flattenStyle([styles.base, this.props.style]);
|
||||
invariant(style, "style must be initialized");
|
||||
var source = this.props.source;
|
||||
invariant(source, "source must be initialized");
|
||||
var source = resolveAssetSource(this.props.source);
|
||||
invariant(source, 'source must be initialized');
|
||||
|
||||
var {width, height} = source;
|
||||
var style = flattenStyle([{width, height}, styles.base, this.props.style]);
|
||||
invariant(style, 'style must be initialized');
|
||||
|
||||
var isNetwork = source.uri && source.uri.match(/^https?:/);
|
||||
invariant(
|
||||
!(isNetwork && source.isStatic),
|
||||
@ -156,12 +157,6 @@ var Image = React.createClass({
|
||||
contentMode,
|
||||
tintColor: style.tintColor,
|
||||
});
|
||||
if (Platform.OS === 'android') {
|
||||
// TODO: update android native code to not need this
|
||||
nativeProps.resizeMode = contentMode;
|
||||
delete nativeProps.contentMode;
|
||||
}
|
||||
|
||||
if (isStored) {
|
||||
nativeProps.imageTag = source.uri;
|
||||
} else {
|
||||
@ -180,30 +175,9 @@ var styles = StyleSheet.create({
|
||||
},
|
||||
});
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
var CommonImageViewAttributes = merge(ReactIOSViewAttributes.UIView, {
|
||||
accessible: true,
|
||||
accessibilityLabel: true,
|
||||
capInsets: {diff: insetsDiffer}, // UIEdgeInsets=UIEdgeInsetsZero
|
||||
imageTag: true,
|
||||
resizeMode: true,
|
||||
src: true,
|
||||
testID: PropTypes.string,
|
||||
});
|
||||
var RCTNetworkImage = requireNativeComponent('RCTNetworkImageView', null);
|
||||
var RCTStaticImage = requireNativeComponent('RCTStaticImage', null);
|
||||
|
||||
var RCTStaticImage = createReactIOSNativeComponentClass({
|
||||
validAttributes: merge(CommonImageViewAttributes, { tintColor: true }),
|
||||
uiViewClassName: 'RCTStaticImage',
|
||||
});
|
||||
|
||||
var RCTNetworkImage = createReactIOSNativeComponentClass({
|
||||
validAttributes: merge(CommonImageViewAttributes, { defaultImageSrc: true }),
|
||||
uiViewClassName: 'RCTNetworkImageView',
|
||||
});
|
||||
} else {
|
||||
var RCTStaticImage = requireNativeComponent('RCTStaticImage', null);
|
||||
var RCTNetworkImage = requireNativeComponent('RCTNetworkImageView', null);
|
||||
}
|
||||
var nativeOnlyProps = {
|
||||
src: true,
|
||||
defaultImageSrc: true,
|
||||
|
@ -30,8 +30,8 @@ var ImageResizeMode = keyMirror({
|
||||
cover: null,
|
||||
/**
|
||||
* stretch - The image will be stretched to fill the entire frame of the
|
||||
* view without clipping. This may change the aspect ratio of the image,
|
||||
* distoring it. Only supported on iOS.
|
||||
* view without clipping. This may change the aspect ratio of the image,
|
||||
* distoring it.
|
||||
*/
|
||||
stretch: null,
|
||||
});
|
||||
|
@ -14,7 +14,6 @@
|
||||
static CAKeyframeAnimation *RCTGIFImageWithImageSource(CGImageSourceRef imageSource)
|
||||
{
|
||||
if (!UTTypeConformsTo(CGImageSourceGetType(imageSource), kUTTypeGIF)) {
|
||||
CFRelease(imageSource);
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
@ -240,6 +240,7 @@
|
||||
58B511721A9E6B3D00147676 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_STATIC_ANALYZER_MODE = deep;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
||||
@ -251,6 +252,7 @@
|
||||
);
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = RCTImage;
|
||||
RUN_CLANG_STATIC_ANALYZER = YES;
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Debug;
|
||||
@ -258,6 +260,7 @@
|
||||
58B511731A9E6B3D00147676 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_STATIC_ANALYZER_MODE = deep;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
||||
@ -269,6 +272,7 @@
|
||||
);
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = RCTImage;
|
||||
RUN_CLANG_STATIC_ANALYZER = NO;
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Release;
|
||||
|
135
Libraries/Image/__tests__/resolveAssetSource-test.js
Normal file
135
Libraries/Image/__tests__/resolveAssetSource-test.js
Normal file
@ -0,0 +1,135 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest.dontMock('../resolveAssetSource');
|
||||
|
||||
var resolveAssetSource;
|
||||
var SourceCode;
|
||||
|
||||
function expectResolvesAsset(input, expectedSource) {
|
||||
expect(resolveAssetSource(input)).toEqual(expectedSource);
|
||||
}
|
||||
|
||||
describe('resolveAssetSource', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModuleRegistry();
|
||||
SourceCode = require('NativeModules').SourceCode;
|
||||
resolveAssetSource = require('../resolveAssetSource');
|
||||
});
|
||||
|
||||
it('returns same source for simple static and network images', () => {
|
||||
var source1 = {uri: 'https://www.facebook.com/logo'};
|
||||
expect(resolveAssetSource(source1)).toBe(source1);
|
||||
|
||||
var source2 = {isStatic: true, uri: 'logo'};
|
||||
expect(resolveAssetSource(source2)).toBe(source2);
|
||||
});
|
||||
|
||||
describe('bundle was loaded from network', () => {
|
||||
beforeEach(() => {
|
||||
SourceCode.scriptURL = 'http://10.0.0.1:8081/main.bundle';
|
||||
});
|
||||
|
||||
it('uses network image', () => {
|
||||
expectResolvesAsset({
|
||||
__packager_asset: true,
|
||||
fileSystemLocation: '/root/app/module/a',
|
||||
httpServerLocation: '/assets/module/a',
|
||||
width: 100,
|
||||
height: 200,
|
||||
scales: [1],
|
||||
hash: '5b6f00f',
|
||||
name: 'logo',
|
||||
type: 'png',
|
||||
}, {
|
||||
isStatic: false,
|
||||
width: 100,
|
||||
height: 200,
|
||||
uri: 'http://10.0.0.1:8081/assets/module/a/logo.png?hash=5b6f00f',
|
||||
});
|
||||
});
|
||||
|
||||
it('picks matching scale', () => {
|
||||
expectResolvesAsset({
|
||||
__packager_asset: true,
|
||||
fileSystemLocation: '/root/app/module/a',
|
||||
httpServerLocation: '/assets/module/a',
|
||||
width: 100,
|
||||
height: 200,
|
||||
scales: [1, 2, 3],
|
||||
hash: '5b6f00f',
|
||||
name: 'logo',
|
||||
type: 'png',
|
||||
}, {
|
||||
isStatic: false,
|
||||
width: 100,
|
||||
height: 200,
|
||||
uri: 'http://10.0.0.1:8081/assets/module/a/logo@2x.png?hash=5b6f00f',
|
||||
});
|
||||
});
|
||||
|
||||
it('does not change deprecated assets', () => {
|
||||
expectResolvesAsset({
|
||||
__packager_asset: true,
|
||||
deprecated: true,
|
||||
fileSystemLocation: '/root/app/module/a',
|
||||
httpServerLocation: '/assets/module/a',
|
||||
width: 100,
|
||||
height: 200,
|
||||
scales: [1],
|
||||
hash: '5b6f00f',
|
||||
name: 'logo',
|
||||
type: 'png',
|
||||
}, {
|
||||
isStatic: true,
|
||||
width: 100,
|
||||
height: 200,
|
||||
uri: 'logo',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('bundle was loaded from file', () => {
|
||||
beforeEach(() => {
|
||||
SourceCode.scriptURL = 'file:///Path/To/Simulator/main.bundle';
|
||||
});
|
||||
|
||||
it('uses pre-packed image', () => {
|
||||
expectResolvesAsset({
|
||||
__packager_asset: true,
|
||||
fileSystemLocation: '/root/app/module/a',
|
||||
httpServerLocation: '/assets/module/a',
|
||||
width: 100,
|
||||
height: 200,
|
||||
scales: [1],
|
||||
hash: '5b6f00f',
|
||||
name: 'logo',
|
||||
type: 'png',
|
||||
}, {
|
||||
isStatic: true,
|
||||
width: 100,
|
||||
height: 200,
|
||||
uri: 'assets/module/a/logo.png',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('resolveAssetSource.pickScale', () => {
|
||||
it('picks matching scale', () => {
|
||||
expect(resolveAssetSource.pickScale([1], 2)).toBe(1);
|
||||
expect(resolveAssetSource.pickScale([1, 2, 3], 2)).toBe(2);
|
||||
expect(resolveAssetSource.pickScale([1, 2], 3)).toBe(2);
|
||||
expect(resolveAssetSource.pickScale([1, 2, 3, 4], 3.5)).toBe(4);
|
||||
expect(resolveAssetSource.pickScale([3, 4], 2)).toBe(3);
|
||||
expect(resolveAssetSource.pickScale([], 2)).toBe(1);
|
||||
});
|
||||
});
|
101
Libraries/Image/resolveAssetSource.js
Normal file
101
Libraries/Image/resolveAssetSource.js
Normal file
@ -0,0 +1,101 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule resolveAssetSource
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var PixelRatio = require('PixelRatio');
|
||||
var SourceCode = require('NativeModules').SourceCode;
|
||||
|
||||
var _serverURL;
|
||||
|
||||
function getServerURL() {
|
||||
if (_serverURL === undefined) {
|
||||
var scriptURL = SourceCode.scriptURL;
|
||||
var match = scriptURL && scriptURL.match(/^https?:\/\/.*?\//);
|
||||
if (match) {
|
||||
_serverURL = match[0];
|
||||
} else {
|
||||
_serverURL = null;
|
||||
}
|
||||
}
|
||||
|
||||
return _serverURL;
|
||||
}
|
||||
|
||||
function pickScale(scales, deviceScale) {
|
||||
// Packager guarantees that `scales` array is sorted
|
||||
for (var i = 0; i < scales.length; i++) {
|
||||
if (scales[i] >= deviceScale) {
|
||||
return scales[i];
|
||||
}
|
||||
}
|
||||
|
||||
// If nothing matches, device scale is larger than any available
|
||||
// scales, so we return the biggest one. Unless the array is empty,
|
||||
// in which case we default to 1
|
||||
return scales[scales.length - 1] || 1;
|
||||
}
|
||||
|
||||
// TODO(frantic):
|
||||
// * Pick best scale and append @Nx to file path
|
||||
// * We are currently using httpServerLocation for both http and in-app bundle
|
||||
function resolveAssetSource(source) {
|
||||
if (!source.__packager_asset) {
|
||||
return source;
|
||||
}
|
||||
|
||||
// Deprecated assets are managed by Xcode for now,
|
||||
// just returning image name as `uri`
|
||||
// Examples:
|
||||
// require('image!deprecatd_logo_example')
|
||||
// require('./new-hotness-logo-example.png')
|
||||
if (source.deprecated) {
|
||||
return {
|
||||
width: source.width,
|
||||
height: source.height,
|
||||
isStatic: true,
|
||||
uri: source.name || source.uri, // TODO(frantic): remove uri
|
||||
};
|
||||
}
|
||||
|
||||
// TODO(frantic): currently httpServerLocation is used both as
|
||||
// path in http URL and path within IPA. Should we have zipArchiveLocation?
|
||||
var path = source.httpServerLocation;
|
||||
if (path[0] === '/') {
|
||||
path = path.substr(1);
|
||||
}
|
||||
|
||||
var scale = pickScale(source.scales, PixelRatio.get());
|
||||
var scaleSuffix = scale === 1 ? '' : '@' + scale + 'x';
|
||||
|
||||
var fileName = source.name + scaleSuffix + '.' + source.type;
|
||||
var serverURL = getServerURL();
|
||||
if (serverURL) {
|
||||
return {
|
||||
width: source.width,
|
||||
height: source.height,
|
||||
uri: serverURL + path + '/' + fileName +
|
||||
'?hash=' + source.hash,
|
||||
isStatic: false,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
width: source.width,
|
||||
height: source.height,
|
||||
uri: path + '/' + fileName,
|
||||
isStatic: true,
|
||||
};
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
module.exports = resolveAssetSource;
|
||||
module.exports.pickScale = pickScale;
|
@ -28,7 +28,7 @@ type Exception = {
|
||||
function handleException(e: Exception) {
|
||||
var stack = parseErrorStack(e);
|
||||
console.error(
|
||||
'Error: ' +
|
||||
'Err0r: ' +
|
||||
'\n stack: \n' + stackToString(stack) +
|
||||
'\n URL: ' + e.sourceURL +
|
||||
'\n line: ' + e.line +
|
||||
|
@ -7,8 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule loadSourceMap
|
||||
*
|
||||
* -- disabled flow due to mysterious validation errors --
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
@ -40,7 +39,10 @@ function fetchSourceMap(): Promise {
|
||||
.then(response => response.text())
|
||||
}
|
||||
|
||||
function extractSourceMapURL({url, text}): string {
|
||||
function extractSourceMapURL({url, text, fullSourceMappingURL}): string {
|
||||
if (fullSourceMappingURL) {
|
||||
return fullSourceMappingURL;
|
||||
}
|
||||
var mapURL = SourceMapURL.getFrom(text);
|
||||
var baseURL = url.match(/(.+:\/\/.*?)\//)[1];
|
||||
return baseURL + mapURL;
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "../../React/Base/RCTBridgeModule.h"
|
||||
#import "RCTBridgeModule.h"
|
||||
|
||||
@interface RCTLinkingManager : NSObject <RCTBridgeModule>
|
||||
|
||||
|
@ -36,6 +36,11 @@ RCT_EXPORT_MODULE()
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
- (dispatch_queue_t)methodQueue
|
||||
{
|
||||
return dispatch_queue_create("com.facebook.React.LinkingManager", DISPATCH_QUEUE_SERIAL);
|
||||
}
|
||||
|
||||
+ (BOOL)application:(UIApplication *)application
|
||||
openURL:(NSURL *)URL
|
||||
sourceApplication:(NSString *)sourceApplication
|
||||
@ -56,12 +61,14 @@ RCT_EXPORT_MODULE()
|
||||
|
||||
RCT_EXPORT_METHOD(openURL:(NSURL *)URL)
|
||||
{
|
||||
// Doesn't really matter what thread we call this on since it exits the app
|
||||
[[UIApplication sharedApplication] openURL:URL];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(canOpenURL:(NSURL *)URL
|
||||
callback:(RCTResponseSenderBlock)callback)
|
||||
{
|
||||
// This can be expensive, so we deliberately don't call on main thread
|
||||
BOOL canOpen = [[UIApplication sharedApplication] canOpenURL:URL];
|
||||
callback(@[@(canOpen)]);
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ type ReachabilityStateIOS = $Enum<{
|
||||
*
|
||||
* ### reachabilityIOS
|
||||
*
|
||||
* Asyncronously determine if the device is online and on a cellular network.
|
||||
* Asynchronously determine if the device is online and on a cellular network.
|
||||
*
|
||||
* - `none` - device is offline
|
||||
* - `wifi` - device is online and connected via wifi, or is the iOS simulator
|
||||
@ -60,7 +60,7 @@ type ReachabilityStateIOS = $Enum<{
|
||||
*
|
||||
* ### isConnected
|
||||
*
|
||||
* Available on all platforms. Asyncronously fetch a boolean to determine
|
||||
* Available on all platforms. Asynchronously fetch a boolean to determine
|
||||
* internet connectivity.
|
||||
*
|
||||
* ```
|
||||
|
@ -204,6 +204,7 @@
|
||||
58B511F01A9E6C8500147676 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_STATIC_ANALYZER_MODE = deep;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
||||
@ -212,6 +213,7 @@
|
||||
LIBRARY_SEARCH_PATHS = "$(inherited)";
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = RCTNetwork;
|
||||
RUN_CLANG_STATIC_ANALYZER = YES;
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Debug;
|
||||
@ -219,6 +221,7 @@
|
||||
58B511F11A9E6C8500147676 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_STATIC_ANALYZER_MODE = deep;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
||||
|
@ -25,6 +25,8 @@ static NSString *const RCTReachabilityStateCell = @"cell";
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
static void RCTReachabilityCallback(__unused SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info)
|
||||
{
|
||||
RCTReachability *self = (__bridge id)info;
|
||||
@ -53,8 +55,6 @@ static void RCTReachabilityCallback(__unused SCNetworkReachabilityRef target, SC
|
||||
}
|
||||
}
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (instancetype)initWithHost:(NSString *)host
|
||||
@ -71,7 +71,7 @@ RCT_EXPORT_MODULE()
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
return [self initWithHost:@"http://apple.com"];
|
||||
return [self initWithHost:@"apple.com"];
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
|
@ -53,13 +53,23 @@ class XMLHttpRequestBase {
|
||||
}
|
||||
|
||||
getAllResponseHeaders(): ?string {
|
||||
/* Stub */
|
||||
return '';
|
||||
if (this.responseHeaders) {
|
||||
var headers = [];
|
||||
for (var headerName in this.responseHeaders) {
|
||||
headers.push(headerName + ': ' + this.responseHeaders[headerName]);
|
||||
}
|
||||
return headers.join('\n');
|
||||
}
|
||||
// according to the spec, return null <==> no response has been received
|
||||
return null;
|
||||
}
|
||||
|
||||
getResponseHeader(header: string): ?string {
|
||||
/* Stub */
|
||||
return '';
|
||||
if (this.responseHeaders) {
|
||||
var value = this.responseHeaders[header];
|
||||
return value !== undefined ? value : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
setRequestHeader(header: string, value: any): void {
|
||||
@ -122,7 +132,7 @@ class XMLHttpRequestBase {
|
||||
return;
|
||||
}
|
||||
this.status = status;
|
||||
this.responseHeaders = responseHeaders;
|
||||
this.responseHeaders = responseHeaders || {};
|
||||
this.responseText = responseText;
|
||||
this._setReadyState(this.DONE);
|
||||
this._sendLoad();
|
||||
|
@ -198,6 +198,7 @@
|
||||
58B511F01A9E6C8500147676 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_STATIC_ANALYZER_MODE = deep;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
||||
@ -206,6 +207,7 @@
|
||||
LIBRARY_SEARCH_PATHS = "$(inherited)";
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = RCTPushNotification;
|
||||
RUN_CLANG_STATIC_ANALYZER = YES;
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Debug;
|
||||
@ -213,6 +215,7 @@
|
||||
58B511F11A9E6C8500147676 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_STATIC_ANALYZER_MODE = deep;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "../../React/Base/RCTBridgeModule.h"
|
||||
#import "RCTBridgeModule.h"
|
||||
|
||||
@interface RCTPushNotificationManager : NSObject <RCTBridgeModule>
|
||||
|
||||
|
@ -113,7 +113,7 @@ RCT_EXPORT_METHOD(checkPermissions:(RCTResponseSenderBlock)callback)
|
||||
|
||||
#endif
|
||||
|
||||
NSUInteger types;
|
||||
NSUInteger types = 0;
|
||||
if ([UIApplication instancesRespondToSelector:@selector(currentUserNotificationSettings)]) {
|
||||
types = [[[UIApplication sharedApplication] currentUserNotificationSettings] types];
|
||||
} else {
|
||||
|
@ -246,6 +246,7 @@
|
||||
580C37841AB104AF0015E709 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_STATIC_ANALYZER_MODE = deep;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
||||
@ -257,6 +258,7 @@
|
||||
XCTest,
|
||||
);
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
RUN_CLANG_STATIC_ANALYZER = YES;
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Debug;
|
||||
@ -264,6 +266,7 @@
|
||||
580C37851AB104AF0015E709 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_STATIC_ANALYZER_MODE = deep;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
||||
|
@ -10,19 +10,30 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTBridgeModule.h"
|
||||
#import "RCTDefines.h"
|
||||
|
||||
@class FBSnapshotTestController;
|
||||
|
||||
@interface RCTTestModule : NSObject <RCTBridgeModule>
|
||||
|
||||
// This is typically polled while running the runloop until true
|
||||
@property (nonatomic, readonly, getter=isDone) BOOL done;
|
||||
|
||||
// This is used to give meaningful names to snapshot image files.
|
||||
@property (nonatomic, assign) SEL testSelector;
|
||||
/**
|
||||
* The snapshot test controller for this module.
|
||||
*/
|
||||
@property (nonatomic, weak) FBSnapshotTestController *controller;
|
||||
|
||||
/**
|
||||
* This is the view to be snapshotted.
|
||||
*/
|
||||
@property (nonatomic, weak) UIView *view;
|
||||
|
||||
- (instancetype)initWithSnapshotController:(FBSnapshotTestController *)controller view:(UIView *)view;
|
||||
/**
|
||||
* This is used to give meaningful names to snapshot image files.
|
||||
*/
|
||||
@property (nonatomic, assign) SEL testSelector;
|
||||
|
||||
/**
|
||||
* This is typically polled while running the runloop until true.
|
||||
*/
|
||||
@property (nonatomic, readonly, getter=isDone) BOOL done;
|
||||
|
||||
@end
|
||||
|
@ -12,21 +12,25 @@
|
||||
#import "FBSnapshotTestController.h"
|
||||
#import "RCTAssert.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTUIManager.h"
|
||||
|
||||
@implementation RCTTestModule
|
||||
{
|
||||
__weak FBSnapshotTestController *_snapshotController;
|
||||
__weak UIView *_view;
|
||||
NSMutableDictionary *_snapshotCounter;
|
||||
}
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (instancetype)initWithSnapshotController:(FBSnapshotTestController *)controller view:(UIView *)view
|
||||
- (dispatch_queue_t)methodQueue
|
||||
{
|
||||
return _bridge.uiManager.methodQueue;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_snapshotController = controller;
|
||||
_view = view;
|
||||
_snapshotCounter = [NSMutableDictionary new];
|
||||
}
|
||||
return self;
|
||||
@ -34,30 +38,29 @@ RCT_EXPORT_MODULE()
|
||||
|
||||
RCT_EXPORT_METHOD(verifySnapshot:(RCTResponseSenderBlock)callback)
|
||||
{
|
||||
if (!_snapshotController) {
|
||||
RCTLogWarn(@"No snapshot controller configured.");
|
||||
callback(@[]);
|
||||
return;
|
||||
}
|
||||
RCTAssert(_controller != nil, @"No snapshot controller configured.");
|
||||
|
||||
[_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSString *testName = NSStringFromSelector(_testSelector);
|
||||
_snapshotCounter[testName] = @([_snapshotCounter[testName] integerValue] + 1);
|
||||
_snapshotCounter[testName] = [@([_snapshotCounter[testName] integerValue] + 1) stringValue];
|
||||
|
||||
NSError *error = nil;
|
||||
BOOL success = [_snapshotController compareSnapshotOfView:_view
|
||||
selector:_testSelector
|
||||
identifier:[_snapshotCounter[testName] stringValue]
|
||||
error:&error];
|
||||
BOOL success = [_controller compareSnapshotOfView:_view
|
||||
selector:_testSelector
|
||||
identifier:_snapshotCounter[testName]
|
||||
error:&error];
|
||||
|
||||
RCTAssert(success, @"Snapshot comparison failed: %@", error);
|
||||
callback(@[]);
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(markTestCompleted)
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
||||
_done = YES;
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -10,6 +10,7 @@
|
||||
#import "RCTTestRunner.h"
|
||||
|
||||
#import "FBSnapshotTestController.h"
|
||||
#import "RCTDefines.h"
|
||||
#import "RCTRedBox.h"
|
||||
#import "RCTRootView.h"
|
||||
#import "RCTTestModule.h"
|
||||
@ -19,7 +20,7 @@
|
||||
|
||||
@implementation RCTTestRunner
|
||||
{
|
||||
FBSnapshotTestController *_snapshotController;
|
||||
FBSnapshotTestController *_testController;
|
||||
}
|
||||
|
||||
- (instancetype)initWithApp:(NSString *)app referenceDir:(NSString *)referenceDir
|
||||
@ -27,8 +28,8 @@
|
||||
if ((self = [super init])) {
|
||||
NSString *sanitizedAppName = [app stringByReplacingOccurrencesOfString:@"/" withString:@"-"];
|
||||
sanitizedAppName = [sanitizedAppName stringByReplacingOccurrencesOfString:@"\\" withString:@"-"];
|
||||
_snapshotController = [[FBSnapshotTestController alloc] initWithTestName:sanitizedAppName];
|
||||
_snapshotController.referenceImagesDirectory = referenceDir;
|
||||
_testController = [[FBSnapshotTestController alloc] initWithTestName:sanitizedAppName];
|
||||
_testController.referenceImagesDirectory = referenceDir;
|
||||
_scriptURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:8081/%@.includeRequire.runModule.bundle?dev=true", app]];
|
||||
}
|
||||
return self;
|
||||
@ -36,12 +37,12 @@
|
||||
|
||||
- (void)setRecordMode:(BOOL)recordMode
|
||||
{
|
||||
_snapshotController.recordMode = recordMode;
|
||||
_testController.recordMode = recordMode;
|
||||
}
|
||||
|
||||
- (BOOL)recordMode
|
||||
{
|
||||
return _snapshotController.recordMode;
|
||||
return _testController.recordMode;
|
||||
}
|
||||
|
||||
- (void)runTest:(SEL)test module:(NSString *)moduleName
|
||||
@ -59,27 +60,24 @@
|
||||
|
||||
- (void)runTest:(SEL)test module:(NSString *)moduleName initialProps:(NSDictionary *)initialProps expectErrorBlock:(BOOL(^)(NSString *error))expectErrorBlock
|
||||
{
|
||||
UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
|
||||
if ([vc.view isKindOfClass:[RCTRootView class]]) {
|
||||
[(RCTRootView *)vc.view invalidate]; // Make sure the normal app view doesn't interfere
|
||||
}
|
||||
vc.view = [[UIView alloc] init];
|
||||
|
||||
RCTTestModule *testModule = [[RCTTestModule alloc] initWithSnapshotController:_snapshotController view:nil];
|
||||
testModule.testSelector = test;
|
||||
RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_scriptURL
|
||||
moduleProvider:^(){
|
||||
return @[testModule];
|
||||
}
|
||||
launchOptions:nil];
|
||||
|
||||
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
|
||||
moduleName:moduleName];
|
||||
testModule.view = rootView;
|
||||
[vc.view addSubview:rootView]; // Add as subview so it doesn't get resized
|
||||
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:_scriptURL
|
||||
moduleName:moduleName
|
||||
launchOptions:nil];
|
||||
rootView.initialProperties = initialProps;
|
||||
rootView.frame = CGRectMake(0, 0, 320, 2000); // Constant size for testing on multiple devices
|
||||
|
||||
NSString *testModuleName = RCTBridgeModuleNameForClass([RCTTestModule class]);
|
||||
RCTTestModule *testModule = rootView.bridge.modules[testModuleName];
|
||||
testModule.controller = _testController;
|
||||
testModule.testSelector = test;
|
||||
testModule.view = rootView;
|
||||
|
||||
UIViewController *vc = [UIApplication sharedApplication].delegate.window.rootViewController;
|
||||
vc.view = [[UIView alloc] init];
|
||||
[vc.view addSubview:rootView]; // Add as subview so it doesn't get resized
|
||||
|
||||
#if RCT_DEBUG // Prevents build errors, as RCTRedBox is underfined if RCT_DEBUG=0
|
||||
|
||||
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
|
||||
NSString *error = [[RCTRedBox sharedInstance] currentErrorMessage];
|
||||
while ([date timeIntervalSinceNow] > 0 && ![testModule isDone] && error == nil) {
|
||||
@ -100,6 +98,13 @@
|
||||
} else {
|
||||
RCTAssert([testModule isDone], @"Test didn't finish within %d seconds", TIMEOUT_SECONDS);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
expectErrorBlock(@"RCTRedBox unavailable. Set RCT_DEBUG=1 for testing.");
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
00D277161AB8C32C00DC1E48 /* RCTWebSocketExecutor.m in Sources */ = {isa = PBXBuildFile; fileRef = 00D277151AB8C32C00DC1E48 /* RCTWebSocketExecutor.m */; };
|
||||
00D277191AB8C35800DC1E48 /* SRWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 00D277181AB8C35800DC1E48 /* SRWebSocket.m */; };
|
||||
13AF20421AE707C5005F5298 /* SRWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 13AF20411AE707C5005F5298 /* SRWebSocket.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
@ -26,8 +26,8 @@
|
||||
/* Begin PBXFileReference section */
|
||||
00D277141AB8C32C00DC1E48 /* RCTWebSocketExecutor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebSocketExecutor.h; sourceTree = "<group>"; };
|
||||
00D277151AB8C32C00DC1E48 /* RCTWebSocketExecutor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWebSocketExecutor.m; sourceTree = "<group>"; };
|
||||
00D277171AB8C35800DC1E48 /* SRWebSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRWebSocket.h; sourceTree = "<group>"; };
|
||||
00D277181AB8C35800DC1E48 /* SRWebSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRWebSocket.m; sourceTree = "<group>"; };
|
||||
13AF20401AE707C5005F5298 /* SRWebSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRWebSocket.h; sourceTree = "<group>"; };
|
||||
13AF20411AE707C5005F5298 /* SRWebSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRWebSocket.m; sourceTree = "<group>"; };
|
||||
832C81801AAF6DEF007FA2F7 /* libRCTWebSocketDebugger.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTWebSocketDebugger.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
@ -45,8 +45,8 @@
|
||||
832C81771AAF6DEF007FA2F7 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
00D277171AB8C35800DC1E48 /* SRWebSocket.h */,
|
||||
00D277181AB8C35800DC1E48 /* SRWebSocket.m */,
|
||||
13AF20401AE707C5005F5298 /* SRWebSocket.h */,
|
||||
13AF20411AE707C5005F5298 /* SRWebSocket.m */,
|
||||
00D277141AB8C32C00DC1E48 /* RCTWebSocketExecutor.h */,
|
||||
00D277151AB8C32C00DC1E48 /* RCTWebSocketExecutor.m */,
|
||||
832C81811AAF6DEF007FA2F7 /* Products */,
|
||||
@ -119,7 +119,7 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
00D277191AB8C35800DC1E48 /* SRWebSocket.m in Sources */,
|
||||
13AF20421AE707C5005F5298 /* SRWebSocket.m in Sources */,
|
||||
00D277161AB8C32C00DC1E48 /* RCTWebSocketExecutor.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -214,6 +214,7 @@
|
||||
832C81951AAF6DF0007FA2F7 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_STATIC_ANALYZER_MODE = deep;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
||||
@ -224,6 +225,7 @@
|
||||
"-llibicucore",
|
||||
);
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
RUN_CLANG_STATIC_ANALYZER = YES;
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Debug;
|
||||
@ -231,6 +233,7 @@
|
||||
832C81961AAF6DF0007FA2F7 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_STATIC_ANALYZER_MODE = deep;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
||||
|
@ -7,6 +7,10 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTDefines.h"
|
||||
|
||||
#if RCT_DEV // Debug executors are only supported in dev mode
|
||||
|
||||
#import "RCTJavaScriptExecutor.h"
|
||||
|
||||
@interface RCTWebSocketExecutor : NSObject <RCTJavaScriptExecutor>
|
||||
@ -14,3 +18,5 @@
|
||||
- (instancetype)initWithURL:(NSURL *)URL;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
|
@ -7,6 +7,10 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTDefines.h"
|
||||
|
||||
#if RCT_DEV // Debug executors are only supported in dev mode
|
||||
|
||||
#import "RCTWebSocketExecutor.h"
|
||||
|
||||
#import "RCTLog.h"
|
||||
@ -82,7 +86,7 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply);
|
||||
{
|
||||
__block NSError *initError;
|
||||
dispatch_semaphore_t s = dispatch_semaphore_create(0);
|
||||
[self sendMessage:@{@"method": @"prepareJSRuntime"} waitForReply:^(NSError *error, NSDictionary *reply) {
|
||||
[self sendMessage:@{@"method": @"prepareJSRuntime"} context:nil waitForReply:^(NSError *error, NSDictionary *reply) {
|
||||
initError = error;
|
||||
dispatch_semaphore_signal(s);
|
||||
}];
|
||||
@ -111,7 +115,7 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply);
|
||||
RCTLogError(@"WebSocket connection failed with error %@", error);
|
||||
}
|
||||
|
||||
- (void)sendMessage:(NSDictionary *)message waitForReply:(WSMessageCallback)callback
|
||||
- (void)sendMessage:(NSDictionary *)message context:(NSNumber *)executorID waitForReply:(WSMessageCallback)callback
|
||||
{
|
||||
static NSUInteger lastID = 10000;
|
||||
|
||||
@ -122,6 +126,8 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply);
|
||||
}];
|
||||
callback(error, nil);
|
||||
return;
|
||||
} else if (executorID && ![RCTGetExecutorID(self) isEqualToNumber:executorID]) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSNumber *expectedID = @(lastID++);
|
||||
@ -134,22 +140,22 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply);
|
||||
|
||||
- (void)executeApplicationScript:(NSString *)script sourceURL:(NSURL *)URL onComplete:(RCTJavaScriptCompleteBlock)onComplete
|
||||
{
|
||||
NSDictionary *message = @{@"method": NSStringFromSelector(_cmd), @"url": [URL absoluteString], @"inject": _injectedObjects};
|
||||
[self sendMessage:message waitForReply:^(NSError *error, NSDictionary *reply) {
|
||||
NSDictionary *message = @{@"method": @"executeApplicationScript", @"url": [URL absoluteString], @"inject": _injectedObjects};
|
||||
[self sendMessage:message context:nil waitForReply:^(NSError *error, NSDictionary *reply) {
|
||||
onComplete(error);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)executeJSCall:(NSString *)name method:(NSString *)method arguments:(NSArray *)arguments callback:(RCTJavaScriptCallback)onComplete
|
||||
- (void)executeJSCall:(NSString *)name method:(NSString *)method arguments:(NSArray *)arguments context:(NSNumber *)executorID callback:(RCTJavaScriptCallback)onComplete
|
||||
{
|
||||
RCTAssert(onComplete != nil, @"callback was missing for exec JS call");
|
||||
NSDictionary *message = @{
|
||||
@"method": NSStringFromSelector(_cmd),
|
||||
@"method": @"executeJSCall",
|
||||
@"moduleName": name,
|
||||
@"moduleMethod": method,
|
||||
@"arguments": arguments
|
||||
};
|
||||
[self sendMessage:message waitForReply:^(NSError *socketError, NSDictionary *reply) {
|
||||
[self sendMessage:message context:executorID waitForReply:^(NSError *socketError, NSDictionary *reply) {
|
||||
if (socketError) {
|
||||
onComplete(nil, socketError);
|
||||
return;
|
||||
@ -169,6 +175,11 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply);
|
||||
});
|
||||
}
|
||||
|
||||
- (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), block);
|
||||
}
|
||||
|
||||
- (void)invalidate
|
||||
{
|
||||
_socket.delegate = nil;
|
||||
@ -187,3 +198,5 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
|
@ -19,6 +19,8 @@
|
||||
|
||||
#import <Availability.h>
|
||||
|
||||
#pragma clang diagnostic ignored "-Wshadow"
|
||||
|
||||
//NOTE: libicucore ins't actually needed for the socket to function
|
||||
//and by commenting this out, we avoid the need to import it into every app.
|
||||
|
||||
@ -1702,7 +1704,7 @@ static inline int32_t validate_dispatch_data_partial_string(NSData *data) {
|
||||
for (int i = 0; i < maxCodepointSize; i++) {
|
||||
NSString *str = [[NSString alloc] initWithBytesNoCopy:(char *)data.bytes length:data.length - i encoding:NSUTF8StringEncoding freeWhenDone:NO];
|
||||
if (str) {
|
||||
return data.length - i;
|
||||
return (int32_t)(data.length - i);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,7 @@ var TextInputState = require('TextInputState');
|
||||
var flattenStyle = require('flattenStyle');
|
||||
var invariant = require('invariant');
|
||||
var mergeFast = require('mergeFast');
|
||||
var precomputeStyle = require('precomputeStyle');
|
||||
|
||||
type MeasureOnSuccessCallback = (
|
||||
x: number,
|
||||
@ -93,7 +94,7 @@ var NativeMethodsMixin = {
|
||||
break;
|
||||
}
|
||||
}
|
||||
var style = flattenStyle(nativeProps.style);
|
||||
var style = precomputeStyle(flattenStyle(nativeProps.style));
|
||||
|
||||
var props = null;
|
||||
if (hasOnlyStyle) {
|
||||
|
@ -23,6 +23,7 @@ var styleDiffer = require('styleDiffer');
|
||||
var deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev');
|
||||
var diffRawProperties = require('diffRawProperties');
|
||||
var flattenStyle = require('flattenStyle');
|
||||
var precomputeStyle = require('precomputeStyle');
|
||||
var warning = require('warning');
|
||||
|
||||
var registrationNames = ReactIOSEventEmitter.registrationNames;
|
||||
@ -160,7 +161,7 @@ ReactIOSNativeComponent.Mixin = {
|
||||
// before actually doing the expensive flattening operation in order to
|
||||
// compute the diff.
|
||||
if (styleDiffer(nextProps.style, prevProps.style)) {
|
||||
var nextFlattenedStyle = flattenStyle(nextProps.style);
|
||||
var nextFlattenedStyle = precomputeStyle(flattenStyle(nextProps.style));
|
||||
updatePayload = diffRawProperties(
|
||||
updatePayload,
|
||||
this.previousFlattenedStyle,
|
||||
|
@ -41,18 +41,19 @@ function requireNativeComponent(
|
||||
viewName: string,
|
||||
wrapperComponent: ?Function
|
||||
): Function {
|
||||
var viewConfig = RCTUIManager.viewConfigs && RCTUIManager.viewConfigs[viewName];
|
||||
if (!viewConfig) {
|
||||
var viewConfig = RCTUIManager[viewName];
|
||||
if (!viewConfig || !viewConfig.nativeProps) {
|
||||
return UnimplementedView;
|
||||
}
|
||||
var nativeProps = {
|
||||
...RCTUIManager.viewConfigs.RCTView.nativeProps,
|
||||
...RCTUIManager.RCTView.nativeProps,
|
||||
...viewConfig.nativeProps,
|
||||
};
|
||||
viewConfig.uiViewClassName = viewName;
|
||||
viewConfig.validAttributes = {};
|
||||
for (var key in nativeProps) {
|
||||
// TODO: deep diff by default in diffRawProperties instead of setting it here
|
||||
var differ = TypeToDifferMap[nativeProps[key].type] || deepDiffer;
|
||||
var differ = TypeToDifferMap[nativeProps[key]] || deepDiffer;
|
||||
viewConfig.validAttributes[key] = {diff: differ};
|
||||
}
|
||||
if (__DEV__) {
|
||||
|
@ -23,7 +23,7 @@ function verifyPropTypes(
|
||||
return; // This happens for UnimplementedView.
|
||||
}
|
||||
var nativeProps = viewConfig.nativeProps;
|
||||
for (var prop in viewConfig.nativeProps) {
|
||||
for (var prop in nativeProps) {
|
||||
if (!component.propTypes[prop] &&
|
||||
!View.propTypes[prop] &&
|
||||
!ReactIOSStyleAttributes[prop] &&
|
||||
|
161
Libraries/StyleSheet/precomputeStyle.js
Normal file
161
Libraries/StyleSheet/precomputeStyle.js
Normal file
@ -0,0 +1,161 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule precomputeStyle
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var MatrixMath = require('MatrixMath');
|
||||
var deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev');
|
||||
var invariant = require('invariant');
|
||||
|
||||
/**
|
||||
* This method provides a hook where flattened styles may be precomputed or
|
||||
* otherwise prepared to become better input data for native code.
|
||||
*/
|
||||
function precomputeStyle(style: ?Object): ?Object {
|
||||
if (!style || !style.transform) {
|
||||
return style;
|
||||
}
|
||||
invariant(
|
||||
!style.transformMatrix,
|
||||
'transformMatrix and transform styles cannot be used on the same component'
|
||||
);
|
||||
var newStyle = _precomputeTransforms({...style});
|
||||
deepFreezeAndThrowOnMutationInDev(newStyle);
|
||||
return newStyle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a transform matrix based on the provided transforms, and use that
|
||||
* within the style object instead.
|
||||
*
|
||||
* This allows us to provide an API that is similar to CSS and to have a
|
||||
* universal, singular interface to native code.
|
||||
*/
|
||||
function _precomputeTransforms(style: Object): Object {
|
||||
var {transform} = style;
|
||||
var result = MatrixMath.createIdentityMatrix();
|
||||
|
||||
transform.forEach(transformation => {
|
||||
var key = Object.keys(transformation)[0];
|
||||
var value = transformation[key];
|
||||
if (__DEV__) {
|
||||
_validateTransform(key, value, transformation);
|
||||
}
|
||||
|
||||
switch (key) {
|
||||
case 'matrix':
|
||||
MatrixMath.multiplyInto(result, result, value);
|
||||
break;
|
||||
case 'rotate':
|
||||
_multiplyTransform(result, MatrixMath.reuseRotateZCommand, [_convertToRadians(value)]);
|
||||
break;
|
||||
case 'scale':
|
||||
_multiplyTransform(result, MatrixMath.reuseScaleCommand, [value]);
|
||||
break;
|
||||
case 'scaleX':
|
||||
_multiplyTransform(result, MatrixMath.reuseScaleXCommand, [value]);
|
||||
break;
|
||||
case 'scaleY':
|
||||
_multiplyTransform(result, MatrixMath.reuseScaleYCommand, [value]);
|
||||
break;
|
||||
case 'translate':
|
||||
_multiplyTransform(result, MatrixMath.reuseTranslate3dCommand, [value[0], value[1], value[2] || 0]);
|
||||
break;
|
||||
case 'translateX':
|
||||
_multiplyTransform(result, MatrixMath.reuseTranslate2dCommand, [value, 0]);
|
||||
break;
|
||||
case 'translateY':
|
||||
_multiplyTransform(result, MatrixMath.reuseTranslate2dCommand, [0, value]);
|
||||
break;
|
||||
default:
|
||||
throw new Error('Invalid transform name: ' + key);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
...style,
|
||||
transformMatrix: result,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a destructive operation on a transform matrix.
|
||||
*/
|
||||
function _multiplyTransform(
|
||||
result: Array<number>,
|
||||
matrixMathFunction: Function,
|
||||
args: Array<number>
|
||||
): void {
|
||||
var matrixToApply = MatrixMath.createIdentityMatrix();
|
||||
var argsWithIdentity = [matrixToApply].concat(args);
|
||||
matrixMathFunction.apply(this, argsWithIdentity);
|
||||
MatrixMath.multiplyInto(result, result, matrixToApply);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a string like '0.5rad' or '60deg' into radians expressed in a float.
|
||||
* Note that validation on the string is done in `_validateTransform()`.
|
||||
*/
|
||||
function _convertToRadians(value: string): number {
|
||||
var floatValue = parseFloat(value, 10);
|
||||
return value.indexOf('rad') > -1 ? floatValue : floatValue * Math.PI / 180;
|
||||
}
|
||||
|
||||
function _validateTransform(key, value, transformation) {
|
||||
var multivalueTransforms = [
|
||||
'matrix',
|
||||
'translate',
|
||||
];
|
||||
if (multivalueTransforms.indexOf(key) !== -1) {
|
||||
invariant(
|
||||
Array.isArray(value),
|
||||
'Transform with key of %s must have an array as the value: %s',
|
||||
key,
|
||||
JSON.stringify(transformation)
|
||||
);
|
||||
}
|
||||
switch (key) {
|
||||
case 'matrix':
|
||||
invariant(
|
||||
value.length === 9 || value.length === 16,
|
||||
'Matrix transform must have a length of 9 (2d) or 16 (3d). ' +
|
||||
'Provided matrix has a length of %s: %s',
|
||||
value.length,
|
||||
JSON.stringify(transformation)
|
||||
);
|
||||
break;
|
||||
case 'translate':
|
||||
break;
|
||||
case 'rotate':
|
||||
invariant(
|
||||
typeof value === 'string',
|
||||
'Transform with key of "%s" must be a string: %s',
|
||||
key,
|
||||
JSON.stringify(transformation)
|
||||
);
|
||||
invariant(
|
||||
value.indexOf('deg') > -1 || value.indexOf('rad') > -1,
|
||||
'Rotate transform must be expressed in degrees (deg) or radians ' +
|
||||
'(rad): %s',
|
||||
JSON.stringify(transformation)
|
||||
);
|
||||
break;
|
||||
default:
|
||||
invariant(
|
||||
typeof value === 'number',
|
||||
'Transform with key of "%s" must be a number: %s',
|
||||
key,
|
||||
JSON.stringify(transformation)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = precomputeStyle;
|
@ -22,15 +22,21 @@ static css_dim_t RCTMeasure(void *context, float width)
|
||||
RCTShadowText *shadowText = (__bridge RCTShadowText *)context;
|
||||
|
||||
NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:[shadowText attributedString]];
|
||||
NSTextStorage *previousTextStorage = shadowText.layoutManager.textStorage;
|
||||
if (previousTextStorage) {
|
||||
[previousTextStorage removeLayoutManager:shadowText.layoutManager];
|
||||
}
|
||||
[textStorage addLayoutManager:shadowText.layoutManager];
|
||||
|
||||
shadowText.textContainer.size = CGSizeMake(isnan(width) ? CGFLOAT_MAX : width, CGFLOAT_MAX);
|
||||
shadowText.layoutManager.textStorage = textStorage;
|
||||
[shadowText.layoutManager ensureLayoutForTextContainer:shadowText.textContainer];
|
||||
|
||||
CGSize computedSize = [shadowText.layoutManager usedRectForTextContainer:shadowText.textContainer].size;
|
||||
|
||||
[textStorage removeLayoutManager:shadowText.layoutManager];
|
||||
if (previousTextStorage) {
|
||||
[previousTextStorage addLayoutManager:shadowText.layoutManager];
|
||||
}
|
||||
|
||||
css_dim_t result;
|
||||
result.dimensions[CSS_WIDTH] = RCTCeilPixelValue(computedSize.width);
|
||||
|
@ -25,6 +25,7 @@
|
||||
if ((self = [super initWithFrame:frame])) {
|
||||
_textStorage = [[NSTextStorage alloc] init];
|
||||
|
||||
self.opaque = NO;
|
||||
self.contentMode = UIViewContentModeRedraw;
|
||||
}
|
||||
|
||||
@ -94,21 +95,18 @@
|
||||
return UIEdgeInsetsInsetRect(self.bounds, _contentInset);
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
|
||||
// The header comment for `size` says that a height of 0.0 should be enough,
|
||||
// but it isn't.
|
||||
_textContainer.size = CGSizeMake([self textFrame].size.width, CGFLOAT_MAX);
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect
|
||||
{
|
||||
CGPoint origin = [self textFrame].origin;
|
||||
CGRect textFrame = [self textFrame];
|
||||
|
||||
// We reset the text container size every time because RCTShadowText's
|
||||
// RCTMeasure overrides it. The header comment for `size` says that a height
|
||||
// of 0.0 should be enough, but it isn't.
|
||||
_textContainer.size = CGSizeMake(textFrame.size.width, CGFLOAT_MAX);
|
||||
|
||||
NSRange glyphRange = [_layoutManager glyphRangeForTextContainer:_textContainer];
|
||||
[_layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:origin];
|
||||
[_layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:origin];
|
||||
[_layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:textFrame.origin];
|
||||
[_layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:textFrame.origin];
|
||||
}
|
||||
|
||||
- (NSNumber *)reactTagAtPoint:(CGPoint)point
|
||||
|
@ -222,6 +222,7 @@
|
||||
58B511B01A9E6C1300147676 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_STATIC_ANALYZER_MODE = deep;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
||||
@ -229,6 +230,7 @@
|
||||
);
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
RUN_CLANG_STATIC_ANALYZER = YES;
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Debug;
|
||||
@ -236,6 +238,7 @@
|
||||
58B511B11A9E6C1300147676 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_STATIC_ANALYZER_MODE = deep;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
||||
|
@ -46,7 +46,7 @@ RCT_EXPORT_SHADOW_PROPERTY(fontWeight, NSString)
|
||||
RCT_EXPORT_SHADOW_PROPERTY(fontStyle, NSString)
|
||||
RCT_EXPORT_SHADOW_PROPERTY(isHighlighted, BOOL)
|
||||
RCT_EXPORT_SHADOW_PROPERTY(lineHeight, CGFloat)
|
||||
RCT_EXPORT_SHADOW_PROPERTY(maxNumberOfLines, NSInteger)
|
||||
RCT_EXPORT_SHADOW_PROPERTY(maximumNumberOfLines, NSInteger)
|
||||
RCT_EXPORT_SHADOW_PROPERTY(shadowOffset, CGSize)
|
||||
RCT_EXPORT_SHADOW_PROPERTY(textAlign, NSTextAlignment)
|
||||
RCT_REMAP_SHADOW_PROPERTY(backgroundColor, textBackgroundColor, UIColor)
|
||||
|
@ -37,7 +37,7 @@ var DEFAULT_BUTTON = {
|
||||
* {text: 'Foo', onPress: () => console.log('Foo Pressed!')},
|
||||
* {text: 'Bar', onPress: () => console.log('Bar Pressed!')},
|
||||
* ]
|
||||
* )}
|
||||
* )
|
||||
* ```
|
||||
*/
|
||||
|
||||
|
131
Libraries/Utilities/MatrixMath.js
Executable file
131
Libraries/Utilities/MatrixMath.js
Executable file
@ -0,0 +1,131 @@
|
||||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*
|
||||
* @providesModule MatrixMath
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Memory conservative (mutative) matrix math utilities. Uses "command"
|
||||
* matrices, which are reusable.
|
||||
*/
|
||||
var MatrixMath = {
|
||||
createIdentityMatrix: function() {
|
||||
return [
|
||||
1,0,0,0,
|
||||
0,1,0,0,
|
||||
0,0,1,0,
|
||||
0,0,0,1
|
||||
];
|
||||
},
|
||||
|
||||
createCopy: function(m) {
|
||||
return [
|
||||
m[0], m[1], m[2], m[3],
|
||||
m[4], m[5], m[6], m[7],
|
||||
m[8], m[9], m[10], m[11],
|
||||
m[12], m[13], m[14], m[15],
|
||||
];
|
||||
},
|
||||
|
||||
createTranslate2d: function(x, y) {
|
||||
var mat = MatrixMath.createIdentityMatrix();
|
||||
MatrixMath.reuseTranslate2dCommand(mat, x, y);
|
||||
return mat;
|
||||
},
|
||||
|
||||
reuseTranslate2dCommand: function(matrixCommand, x, y) {
|
||||
matrixCommand[12] = x;
|
||||
matrixCommand[13] = y;
|
||||
},
|
||||
|
||||
reuseTranslate3dCommand: function(matrixCommand, x, y, z) {
|
||||
matrixCommand[12] = x;
|
||||
matrixCommand[13] = y;
|
||||
matrixCommand[14] = z;
|
||||
},
|
||||
|
||||
createScale: function(factor) {
|
||||
var mat = MatrixMath.createIdentityMatrix();
|
||||
MatrixMath.reuseScaleCommand(mat, factor);
|
||||
return mat;
|
||||
},
|
||||
|
||||
reuseScaleCommand: function(matrixCommand, factor) {
|
||||
matrixCommand[0] = factor;
|
||||
matrixCommand[5] = factor;
|
||||
},
|
||||
|
||||
reuseScale3dCommand: function(matrixCommand, x, y, z) {
|
||||
matrixCommand[0] = x;
|
||||
matrixCommand[5] = y;
|
||||
matrixCommand[10] = z;
|
||||
},
|
||||
|
||||
reuseScaleXCommand(matrixCommand, factor) {
|
||||
matrixCommand[0] = factor;
|
||||
},
|
||||
|
||||
reuseScaleYCommand(matrixCommand, factor) {
|
||||
matrixCommand[5] = factor;
|
||||
},
|
||||
|
||||
reuseScaleZCommand(matrixCommand, factor) {
|
||||
matrixCommand[10] = factor;
|
||||
},
|
||||
|
||||
reuseRotateYCommand: function(matrixCommand, amount) {
|
||||
matrixCommand[0] = Math.cos(amount);
|
||||
matrixCommand[2] = Math.sin(amount);
|
||||
matrixCommand[8] = Math.sin(-amount);
|
||||
matrixCommand[10] = Math.cos(amount);
|
||||
},
|
||||
|
||||
createRotateZ: function(radians) {
|
||||
var mat = MatrixMath.createIdentityMatrix();
|
||||
MatrixMath.reuseRotateZCommand(mat, radians);
|
||||
return mat;
|
||||
},
|
||||
|
||||
// http://www.w3.org/TR/css3-transforms/#recomposing-to-a-2d-matrix
|
||||
reuseRotateZCommand: function(matrixCommand, radians) {
|
||||
matrixCommand[0] = Math.cos(radians);
|
||||
matrixCommand[1] = Math.sin(radians);
|
||||
matrixCommand[4] = -Math.sin(radians);
|
||||
matrixCommand[5] = Math.cos(radians);
|
||||
},
|
||||
|
||||
multiplyInto: function(out, a, b) {
|
||||
var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3],
|
||||
a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7],
|
||||
a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11],
|
||||
a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15];
|
||||
|
||||
var b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3];
|
||||
out[0] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
|
||||
out[1] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
|
||||
out[2] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
|
||||
out[3] = b0*a03 + b1*a13 + b2*a23 + b3*a33;
|
||||
|
||||
b0 = b[4]; b1 = b[5]; b2 = b[6]; b3 = b[7];
|
||||
out[4] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
|
||||
out[5] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
|
||||
out[6] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
|
||||
out[7] = b0*a03 + b1*a13 + b2*a23 + b3*a33;
|
||||
|
||||
b0 = b[8]; b1 = b[9]; b2 = b[10]; b3 = b[11];
|
||||
out[8] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
|
||||
out[9] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
|
||||
out[10] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
|
||||
out[11] = b0*a03 + b1*a13 + b2*a23 + b3*a33;
|
||||
|
||||
b0 = b[12]; b1 = b[13]; b2 = b[14]; b3 = b[15];
|
||||
out[12] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
|
||||
out[13] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
|
||||
out[14] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
|
||||
out[15] = b0*a03 + b1*a13 + b2*a23 + b3*a33;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
module.exports = MatrixMath;
|
@ -10,7 +10,9 @@
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var ErrorUtils = require('ErrorUtils');
|
||||
var ReactUpdates = require('ReactUpdates');
|
||||
|
||||
var invariant = require('invariant');
|
||||
var warning = require('warning');
|
||||
@ -307,21 +309,23 @@ var MessageQueueMixin = {
|
||||
);
|
||||
},
|
||||
|
||||
processBatch: function (batch) {
|
||||
processBatch: function(batch) {
|
||||
var self = this;
|
||||
batch.forEach(function (call) {
|
||||
invariant(
|
||||
call.module === 'BatchedBridge',
|
||||
'All the calls should pass through the BatchedBridge module'
|
||||
);
|
||||
if (call.method === 'callFunctionReturnFlushedQueue') {
|
||||
self.callFunction.apply(self, call.args);
|
||||
} else if (call.method === 'invokeCallbackAndReturnFlushedQueue') {
|
||||
self.invokeCallback.apply(self, call.args);
|
||||
} else {
|
||||
throw new Error(
|
||||
'Unrecognized method called on BatchedBridge: ' + call.method);
|
||||
}
|
||||
ReactUpdates.batchedUpdates(function() {
|
||||
batch.forEach(function(call) {
|
||||
invariant(
|
||||
call.module === 'BatchedBridge',
|
||||
'All the calls should pass through the BatchedBridge module'
|
||||
);
|
||||
if (call.method === 'callFunctionReturnFlushedQueue') {
|
||||
self.callFunction.apply(self, call.args);
|
||||
} else if (call.method === 'invokeCallbackAndReturnFlushedQueue') {
|
||||
self.invokeCallback.apply(self, call.args);
|
||||
} else {
|
||||
throw new Error(
|
||||
'Unrecognized method called on BatchedBridge: ' + call.method);
|
||||
}
|
||||
});
|
||||
});
|
||||
return this.flushedQueue();
|
||||
},
|
||||
|
@ -36,8 +36,8 @@ var Dimensions = require('Dimensions');
|
||||
*
|
||||
* ```
|
||||
* var image = getImage({
|
||||
* width: 200 * PixelRatio.get(),
|
||||
* height: 100 * PixelRatio.get()
|
||||
* width: PixelRatio.getPixelSizeForLayoutSize(200),
|
||||
* height: PixelRatio.getPixelSizeForLayoutSize(100),
|
||||
* });
|
||||
* <Image source={image} style={{width: 200, height: 100}} />
|
||||
* ```
|
||||
@ -52,10 +52,21 @@ class PixelRatio {
|
||||
* - iPhone 6
|
||||
* - PixelRatio.get() === 3
|
||||
* - iPhone 6 plus
|
||||
* - PixelRatio.get() === 3.5
|
||||
* - Nexus 6
|
||||
*/
|
||||
static get(): number {
|
||||
return Dimensions.get('window').scale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a layout size (dp) to pixel size (px).
|
||||
*
|
||||
* Guaranteed to return an integer number.
|
||||
*/
|
||||
static getPixelSizeForLayoutSize(layoutSize: number): number {
|
||||
return Math.round(layoutSize * PixelRatio.get());
|
||||
}
|
||||
}
|
||||
|
||||
// No-op for iOS, but used on the web. Should not be documented.
|
||||
|
102
Libraries/Utilities/differ/__tests__/deepDiffer-test.js
Normal file
102
Libraries/Utilities/differ/__tests__/deepDiffer-test.js
Normal file
@ -0,0 +1,102 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest.dontMock('deepDiffer');
|
||||
var deepDiffer = require('deepDiffer');
|
||||
|
||||
describe('deepDiffer', function() {
|
||||
it('should diff primitives of the same type', () => {
|
||||
expect(deepDiffer(1, 2)).toBe(true);
|
||||
expect(deepDiffer(42, 42)).toBe(false);
|
||||
expect(deepDiffer('foo', 'bar')).toBe(true);
|
||||
expect(deepDiffer('foo', 'foo')).toBe(false);
|
||||
expect(deepDiffer(true, false)).toBe(true);
|
||||
expect(deepDiffer(false, true)).toBe(true);
|
||||
expect(deepDiffer(true, true)).toBe(false);
|
||||
expect(deepDiffer(false, false)).toBe(false);
|
||||
expect(deepDiffer(null, null)).toBe(false);
|
||||
expect(deepDiffer(undefined, undefined)).toBe(false);
|
||||
});
|
||||
it('should diff primitives of different types', () => {
|
||||
expect(deepDiffer(1, '1')).toBe(true);
|
||||
expect(deepDiffer(true, 'true')).toBe(true);
|
||||
expect(deepDiffer(true, 1)).toBe(true);
|
||||
expect(deepDiffer(false, 0)).toBe(true);
|
||||
expect(deepDiffer(null, undefined)).toBe(true);
|
||||
expect(deepDiffer(null, 0)).toBe(true);
|
||||
expect(deepDiffer(null, false)).toBe(true);
|
||||
expect(deepDiffer(null, '')).toBe(true);
|
||||
expect(deepDiffer(undefined, 0)).toBe(true);
|
||||
expect(deepDiffer(undefined, false)).toBe(true);
|
||||
expect(deepDiffer(undefined, '')).toBe(true);
|
||||
});
|
||||
it('should diff Objects', () => {
|
||||
expect(deepDiffer({}, {})).toBe(false);
|
||||
expect(deepDiffer({}, null)).toBe(true);
|
||||
expect(deepDiffer(null, {})).toBe(true);
|
||||
expect(deepDiffer({a: 1}, {a: 1})).toBe(false);
|
||||
expect(deepDiffer({a: 1}, {a: 2})).toBe(true);
|
||||
expect(deepDiffer({a: 1}, {a: 1, b: null})).toBe(true);
|
||||
expect(deepDiffer({a: 1}, {a: 1, b: 1})).toBe(true);
|
||||
expect(deepDiffer({a: 1, b: 1}, {a: 1})).toBe(true);
|
||||
expect(deepDiffer({a: {A: 1}, b: 1}, {a: {A: 1}, b: 1})).toBe(false);
|
||||
expect(deepDiffer({a: {A: 1}, b: 1}, {a: {A: 2}, b: 1})).toBe(true);
|
||||
expect(deepDiffer(
|
||||
{a: {A: {aA: 1, bB: 1}}, b: 1},
|
||||
{a: {A: {aA: 1, bB: 1}}, b: 1}
|
||||
)).toBe(false);
|
||||
expect(deepDiffer(
|
||||
{a: {A: {aA: 1, bB: 1}}, b: 1},
|
||||
{a: {A: {aA: 1, cC: 1}}, b: 1}
|
||||
)).toBe(true);
|
||||
});
|
||||
it('should diff Arrays', () => {
|
||||
expect(deepDiffer([], [])).toBe(false);
|
||||
expect(deepDiffer([], null)).toBe(true);
|
||||
expect(deepDiffer(null, [])).toBe(true);
|
||||
expect(deepDiffer([42], [42])).toBe(false);
|
||||
expect(deepDiffer([1], [2])).toBe(true);
|
||||
expect(deepDiffer([1, 2, 3], [1, 2, 3])).toBe(false);
|
||||
expect(deepDiffer([1, 2, 3], [1, 2, 4])).toBe(true);
|
||||
expect(deepDiffer([1, 2, 3], [1, 4, 3])).toBe(true);
|
||||
expect(deepDiffer([1, 2, 3, 4], [1, 2, 3])).toBe(true);
|
||||
expect(deepDiffer([1, 2, 3], [1, 2, 3, 4])).toBe(true);
|
||||
expect(deepDiffer([0, null, false, ''], [0, null, false, ''])).toBe(false);
|
||||
expect(deepDiffer([0, null, false, ''], ['', false, null, 0])).toBe(true);
|
||||
});
|
||||
it('should diff mixed types', () => {
|
||||
expect(deepDiffer({}, [])).toBe(true);
|
||||
expect(deepDiffer([], {})).toBe(true);
|
||||
expect(deepDiffer(
|
||||
{a: [{A: {aA: 1, bB: 1}}, 'bar'], c: [1, [false]]},
|
||||
{a: [{A: {aA: 1, bB: 1}}, 'bar'], c: [1, [false]]}
|
||||
)).toBe(false);
|
||||
expect(deepDiffer(
|
||||
{a: [{A: {aA: 1, bB: 1}}, 'bar'], c: [1, [false]]},
|
||||
{a: [{A: {aA: 1, bB: 2}}, 'bar'], c: [1, [false]]}
|
||||
)).toBe(true);
|
||||
expect(deepDiffer(
|
||||
{a: [{A: {aA: 1, bB: 1}}, 'bar'], c: [1, [false]]},
|
||||
{a: [{A: {aA: 1, bB: 1}}, 'bar'], c: [1, [false], null]}
|
||||
)).toBe(true);
|
||||
expect(deepDiffer(
|
||||
{a: [{A: {aA: 1, bB: 1}}, 'bar'], c: [1, [false]]},
|
||||
{a: [{A: {aA: 1, bB: 1}}, ['bar']], c: [1, [false]]}
|
||||
)).toBe(true);
|
||||
});
|
||||
it('should distinguish between proper Array and Object', () => {
|
||||
expect(deepDiffer(['a', 'b'], {0: 'a', 1: 'b', length: 2})).toBe(true);
|
||||
expect(deepDiffer(['a', 'b'], {length: 2, 0: 'a', 1: 'b'})).toBe(true);
|
||||
});
|
||||
it('should diff same object', () => {
|
||||
var obj = [1,[2,3]];
|
||||
expect(deepDiffer(obj, obj)).toBe(false);
|
||||
});
|
||||
});
|
@ -35,16 +35,29 @@ var deepDiffer = function(one: any, two: any): bool {
|
||||
if (one.constructor !== two.constructor) {
|
||||
return true;
|
||||
}
|
||||
for (var key in one) {
|
||||
if (deepDiffer(one[key], two[key])) {
|
||||
if (Array.isArray(one)) {
|
||||
// We know two is also an array because the constructors are equal
|
||||
var len = one.length;
|
||||
if (two.length !== len) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (var twoKey in two) {
|
||||
// The only case we haven't checked yet is keys that are in two but aren't
|
||||
// in one, which means they are different.
|
||||
if (one[twoKey] === undefined && two[twoKey] !== undefined) {
|
||||
return true;
|
||||
for (var ii = 0; ii < len; ii++) {
|
||||
if (deepDiffer(one[ii], two[ii])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (var key in one) {
|
||||
if (deepDiffer(one[key], two[key])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (var twoKey in two) {
|
||||
// The only case we haven't checked yet is keys that are in two but aren't
|
||||
// in one, which means they are different.
|
||||
if (one[twoKey] === undefined && two[twoKey] !== undefined) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
@ -208,6 +208,7 @@
|
||||
832C81951AAF6DF0007FA2F7 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_STATIC_ANALYZER_MODE = deep;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
||||
@ -215,6 +216,7 @@
|
||||
);
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
RUN_CLANG_STATIC_ANALYZER = YES;
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Debug;
|
||||
@ -222,6 +224,7 @@
|
||||
832C81961AAF6DF0007FA2F7 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_STATIC_ANALYZER_MODE = deep;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
||||
|
@ -9,21 +9,7 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* By default, only raise an NSAssertion in debug mode
|
||||
* (custom assert functions will still be called).
|
||||
*/
|
||||
#ifndef RCT_ASSERT
|
||||
#if DEBUG
|
||||
#define RCT_ASSERT 1
|
||||
#else
|
||||
#define RCT_ASSERT 0
|
||||
#endif
|
||||
#endif
|
||||
#import "RCTDefines.h"
|
||||
|
||||
/**
|
||||
* The default error domain to be used for React errors.
|
||||
@ -44,13 +30,14 @@ typedef void (^RCTAssertFunction)(
|
||||
/**
|
||||
* Private logging function - ignore this.
|
||||
*/
|
||||
void _RCTAssertFormat(BOOL, const char *, int, const char *, NSString *, ...) NS_FORMAT_FUNCTION(5,6);
|
||||
RCT_EXTERN void _RCTAssertFormat(
|
||||
BOOL, const char *, int, const char *, NSString *, ...) NS_FORMAT_FUNCTION(5,6);
|
||||
|
||||
/**
|
||||
* This is the main assert macro that you should use.
|
||||
*/
|
||||
#define RCTAssert(condition, ...) do { BOOL pass = ((condition) != 0); \
|
||||
if (RCT_ASSERT && !pass) { [[NSAssertionHandler currentHandler] handleFailureInFunction:@(__func__) \
|
||||
if (RCT_NSASSERT && !pass) { [[NSAssertionHandler currentHandler] handleFailureInFunction:@(__func__) \
|
||||
file:@(__FILE__) lineNumber:__LINE__ description:__VA_ARGS__]; } \
|
||||
_RCTAssertFormat(pass, __FILE__, __LINE__, __func__, __VA_ARGS__); \
|
||||
} while (false)
|
||||
@ -66,16 +53,12 @@ _RCTAssertFormat(pass, __FILE__, __LINE__, __func__, __VA_ARGS__); \
|
||||
* macros. You can use these to replace the standard behavior with custom log
|
||||
* functionality.
|
||||
*/
|
||||
void RCTSetAssertFunction(RCTAssertFunction assertFunction);
|
||||
RCTAssertFunction RCTGetAssertFunction(void);
|
||||
RCT_EXTERN void RCTSetAssertFunction(RCTAssertFunction assertFunction);
|
||||
RCT_EXTERN RCTAssertFunction RCTGetAssertFunction(void);
|
||||
|
||||
/**
|
||||
* This appends additional code to the existing assert function, without
|
||||
* replacing the existing functionality. Useful if you just want to forward
|
||||
* assert info to an extra service without changing the default behavior.
|
||||
*/
|
||||
void RCTAddAssertFunction(RCTAssertFunction assertFunction);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
RCT_EXTERN void RCTAddAssertFunction(RCTAssertFunction assertFunction);
|
||||
|
@ -10,6 +10,7 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTBridgeModule.h"
|
||||
#import "RCTDefines.h"
|
||||
#import "RCTFrameUpdate.h"
|
||||
#import "RCTInvalidating.h"
|
||||
#import "RCTJavaScriptExecutor.h"
|
||||
@ -40,7 +41,7 @@ typedef NSArray *(^RCTBridgeModuleProviderBlock)(void);
|
||||
/**
|
||||
* This function returns the module name for a given class.
|
||||
*/
|
||||
extern NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass);
|
||||
RCT_EXTERN NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass);
|
||||
|
||||
/**
|
||||
* Async batched bridge used to communicate with the JavaScript application.
|
||||
@ -62,9 +63,9 @@ extern NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass);
|
||||
/**
|
||||
* This method is used to call functions in the JavaScript application context.
|
||||
* It is primarily intended for use by modules that require two-way communication
|
||||
* with the JavaScript code. Method should be regsitered using the
|
||||
* with the JavaScript code. Method should be registered using the
|
||||
* RCT_IMPORT_METHOD macro below. Attempting to call a method that has not been
|
||||
* registered will result in an error.
|
||||
* registered will result in an error. Safe to call from any thread.
|
||||
*/
|
||||
- (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args;
|
||||
|
||||
@ -88,6 +89,11 @@ static const char *__rct_import_##module##_##method##__ = #module"."#method;
|
||||
url:(NSURL *)url
|
||||
onComplete:(RCTJavaScriptCompleteBlock)onComplete;
|
||||
|
||||
/**
|
||||
* URL of the script that was loaded into the bridge.
|
||||
*/
|
||||
@property (nonatomic, copy) NSURL *bundleURL;
|
||||
|
||||
@property (nonatomic, strong) Class executorClass;
|
||||
|
||||
/**
|
||||
@ -101,13 +107,6 @@ static const char *__rct_import_##module##_##method##__ = #module"."#method;
|
||||
*/
|
||||
@property (nonatomic, copy, readonly) NSDictionary *modules;
|
||||
|
||||
/**
|
||||
* The shadow queue is used to execute callbacks from the JavaScript code. All
|
||||
* native hooks (e.g. exported module methods) will be executed on the shadow
|
||||
* queue.
|
||||
*/
|
||||
@property (nonatomic, readonly) dispatch_queue_t shadowQueue;
|
||||
|
||||
/**
|
||||
* The launch options that were used to initialize the bridge.
|
||||
*/
|
||||
@ -119,17 +118,17 @@ static const char *__rct_import_##module##_##method##__ = #module"."#method;
|
||||
@property (nonatomic, readonly, getter=isLoading) BOOL loading;
|
||||
|
||||
/**
|
||||
* Reload the bundle and reset executor and modules.
|
||||
* Reload the bundle and reset executor & modules. Safe to call from any thread.
|
||||
*/
|
||||
- (void)reload;
|
||||
|
||||
/**
|
||||
* Add a new observer that will be called on every screen refresh
|
||||
* 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
|
||||
* Stop receiving screen refresh updates for the given observer.
|
||||
*/
|
||||
- (void)removeFrameUpdateObserver:(id<RCTFrameUpdateObserver>)observer;
|
||||
|
||||
|
@ -22,6 +22,7 @@
|
||||
#import "RCTJavaScriptLoader.h"
|
||||
#import "RCTKeyCommands.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTProfile.h"
|
||||
#import "RCTRedBox.h"
|
||||
#import "RCTRootView.h"
|
||||
#import "RCTSparseArray.h"
|
||||
@ -48,39 +49,6 @@ typedef NS_ENUM(NSUInteger, RCTBridgeFields) {
|
||||
*/
|
||||
#define BATCHED_BRIDGE 1
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
#define RCT_PROFILE_START() \
|
||||
_Pragma("clang diagnostic push") \
|
||||
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
|
||||
NSTimeInterval __rct_profile_start = CACurrentMediaTime() \
|
||||
_Pragma("clang diagnostic pop")
|
||||
|
||||
#define RCT_PROFILE_END(cat, args, profileName...) \
|
||||
do { \
|
||||
if (_profile) { \
|
||||
[_profileLock lock]; \
|
||||
[_profile addObject:@{ \
|
||||
@"name": [@[profileName] componentsJoinedByString: @"_"], \
|
||||
@"cat": @ #cat, \
|
||||
@"ts": @((NSUInteger)((__rct_profile_start - _startingTime) * 1e6)), \
|
||||
@"dur": @((NSUInteger)((CACurrentMediaTime() - __rct_profile_start) * 1e6)), \
|
||||
@"ph": @"X", \
|
||||
@"pid": @([[NSProcessInfo processInfo] processIdentifier]), \
|
||||
@"tid": [[NSThread currentThread] description], \
|
||||
@"args": args ?: [NSNull null], \
|
||||
}]; \
|
||||
[_profileLock unlock]; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#else
|
||||
|
||||
#define RCT_PROFILE_START(...)
|
||||
#define RCT_PROFILE_END(...)
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef __LP64__
|
||||
typedef uint64_t RCTHeaderValue;
|
||||
typedef struct section_64 RCTHeaderSection;
|
||||
@ -175,7 +143,8 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
|
||||
|
||||
// Get data entry
|
||||
NSString *entry = @(*(const char **)(mach_header + addr));
|
||||
NSArray *parts = [[entry substringWithRange:(NSRange){2, entry.length - 3}] componentsSeparatedByString:@" "];
|
||||
NSArray *parts = [[entry substringWithRange:(NSRange){2, entry.length - 3}]
|
||||
componentsSeparatedByString:@" "];
|
||||
|
||||
// Parse class name
|
||||
NSString *moduleClassName = parts[0];
|
||||
@ -196,33 +165,32 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
if (RCT_DEBUG) {
|
||||
|
||||
// We may be able to get rid of this check in future, once people
|
||||
// get used to the new registration system. That would potentially
|
||||
// allow you to create modules that are not automatically registered
|
||||
// We may be able to get rid of this check in future, once people
|
||||
// get used to the new registration system. That would potentially
|
||||
// allow you to create modules that are not automatically registered
|
||||
|
||||
static unsigned int classCount;
|
||||
Class *classes = objc_copyClassList(&classCount);
|
||||
for (unsigned int i = 0; i < classCount; i++)
|
||||
{
|
||||
Class cls = classes[i];
|
||||
Class superclass = cls;
|
||||
while (superclass)
|
||||
static unsigned int classCount;
|
||||
Class *classes = objc_copyClassList(&classCount);
|
||||
for (unsigned int i = 0; i < classCount; i++)
|
||||
{
|
||||
if (class_conformsToProtocol(superclass, @protocol(RCTBridgeModule)))
|
||||
Class cls = classes[i];
|
||||
Class superclass = cls;
|
||||
while (superclass)
|
||||
{
|
||||
if (![RCTModuleClassesByID containsObject:cls]) {
|
||||
RCTLogError(@"Class %@ was not exported. Did you forget to use RCT_EXPORT_MODULE()?", NSStringFromClass(cls));
|
||||
if (class_conformsToProtocol(superclass, @protocol(RCTBridgeModule)))
|
||||
{
|
||||
if (![RCTModuleClassesByID containsObject:cls]) {
|
||||
RCTLogError(@"Class %@ was not exported. Did you forget to use RCT_EXPORT_MODULE()?", NSStringFromClass(cls));
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
superclass = class_getSuperclass(superclass);
|
||||
}
|
||||
superclass = class_getSuperclass(superclass);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
});
|
||||
|
||||
return RCTModuleClassesByID;
|
||||
@ -230,16 +198,15 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
|
||||
|
||||
@interface RCTBridge ()
|
||||
|
||||
@property (nonatomic, copy, readonly) NSArray *profile;
|
||||
|
||||
- (void)_invokeAndProcessModule:(NSString *)module
|
||||
method:(NSString *)method
|
||||
arguments:(NSArray *)args;
|
||||
arguments:(NSArray *)args
|
||||
context:(NSNumber *)context;
|
||||
|
||||
- (void)_actuallyInvokeAndProcessModule:(NSString *)module
|
||||
method:(NSString *)method
|
||||
arguments:(NSArray *)args;
|
||||
|
||||
arguments:(NSArray *)args
|
||||
context:(NSNumber *)context;
|
||||
@end
|
||||
|
||||
/**
|
||||
@ -249,6 +216,7 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
|
||||
|
||||
@property (nonatomic, copy, readonly) NSString *moduleClassName;
|
||||
@property (nonatomic, copy, readonly) NSString *JSMethodName;
|
||||
@property (nonatomic, assign, readonly) SEL selector;
|
||||
|
||||
@end
|
||||
|
||||
@ -260,6 +228,7 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
|
||||
NSMethodSignature *_methodSignature;
|
||||
NSArray *_argumentBlocks;
|
||||
NSString *_methodName;
|
||||
dispatch_block_t _methodQueue;
|
||||
}
|
||||
|
||||
static Class _globalExecutorClass;
|
||||
@ -320,13 +289,13 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
|
||||
_isClassMethod = [reactMethodName characterAtIndex:0] == '+';
|
||||
_moduleClass = NSClassFromString(_moduleClassName);
|
||||
|
||||
#if DEBUG
|
||||
if (RCT_DEBUG) {
|
||||
|
||||
// Sanity check
|
||||
RCTAssert([_moduleClass conformsToProtocol:@protocol(RCTBridgeModule)],
|
||||
@"You are attempting to export the method %@, but %@ does not \
|
||||
conform to the RCTBridgeModule Protocol", objCMethodName, _moduleClassName);
|
||||
#endif
|
||||
// Sanity check
|
||||
RCTAssert([_moduleClass conformsToProtocol:@protocol(RCTBridgeModule)],
|
||||
@"You are attempting to export the method %@, but %@ does not \
|
||||
conform to the RCTBridgeModule Protocol", objCMethodName, _moduleClassName);
|
||||
}
|
||||
|
||||
// Get method signature
|
||||
_methodSignature = _isClassMethod ?
|
||||
@ -338,14 +307,15 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
|
||||
NSMutableArray *argumentBlocks = [[NSMutableArray alloc] initWithCapacity:numberOfArguments - 2];
|
||||
|
||||
#define RCT_ARG_BLOCK(_logic) \
|
||||
[argumentBlocks addObject:^(RCTBridge *bridge, NSInvocation *invocation, NSUInteger index, id json) { \
|
||||
[argumentBlocks addObject:^(RCTBridge *bridge, NSNumber *context, NSInvocation *invocation, NSUInteger index, id json) { \
|
||||
_logic \
|
||||
[invocation setArgument:&value atIndex:index]; \
|
||||
}]; \
|
||||
|
||||
void (^addBlockArgument)(void) = ^{
|
||||
RCT_ARG_BLOCK(
|
||||
if (json && ![json isKindOfClass:[NSNumber class]]) {
|
||||
|
||||
if (RCT_DEBUG && json && ![json isKindOfClass:[NSNumber class]]) {
|
||||
RCTLogError(@"Argument %tu (%@) of %@.%@ should be a number", index,
|
||||
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName);
|
||||
return;
|
||||
@ -355,7 +325,8 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
|
||||
__autoreleasing id value = (json ? ^(NSArray *args) {
|
||||
[bridge _invokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"invokeCallbackAndReturnFlushedQueue"
|
||||
arguments:@[json, args]];
|
||||
arguments:@[json, args]
|
||||
context:context];
|
||||
} : ^(NSArray *unused) {});
|
||||
)
|
||||
};
|
||||
@ -404,10 +375,14 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
|
||||
RCT_CONVERT_CASE('B', BOOL)
|
||||
RCT_CONVERT_CASE('@', id)
|
||||
RCT_CONVERT_CASE('^', void *)
|
||||
|
||||
case '{':
|
||||
RCTAssert(NO, @"Argument %zd of %C[%@ %@] is defined as %@, however RCT_EXPORT_METHOD() "
|
||||
"does not currently support struct-type arguments.", i - 2,
|
||||
[reactMethodName characterAtIndex:0], _moduleClassName,
|
||||
objCMethodName, argumentName);
|
||||
break;
|
||||
default:
|
||||
defaultCase(argumentType);
|
||||
break;
|
||||
}
|
||||
} else if ([argumentName isEqualToString:@"RCTResponseSenderBlock"]) {
|
||||
addBlockArgument();
|
||||
@ -421,7 +396,7 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
|
||||
#define RCT_CASE(_value, _class, _logic) \
|
||||
case _value: { \
|
||||
RCT_ARG_BLOCK( \
|
||||
if (json && ![json isKindOfClass:[_class class]]) { \
|
||||
if (RCT_DEBUG && json && ![json isKindOfClass:[_class class]]) { \
|
||||
RCTLogError(@"Argument %tu (%@) of %@.%@ should be of type %@", index, \
|
||||
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, [_class class]); \
|
||||
return; \
|
||||
@ -437,7 +412,7 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
|
||||
#define RCT_SIMPLE_CASE(_value, _type, _selector) \
|
||||
case _value: { \
|
||||
RCT_ARG_BLOCK( \
|
||||
if (json && ![json respondsToSelector:@selector(_selector)]) { \
|
||||
if (RCT_DEBUG && json && ![json respondsToSelector:@selector(_selector)]) { \
|
||||
RCTLogError(@"Argument %tu (%@) of %@.%@ does not respond to selector: %@", \
|
||||
index, json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, @#_selector); \
|
||||
return; \
|
||||
@ -463,7 +438,6 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
|
||||
|
||||
default:
|
||||
defaultCase(argumentType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -477,21 +451,21 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
|
||||
- (void)invokeWithBridge:(RCTBridge *)bridge
|
||||
module:(id)module
|
||||
arguments:(NSArray *)arguments
|
||||
context:(NSNumber *)context
|
||||
{
|
||||
if (RCT_DEBUG) {
|
||||
|
||||
#if DEBUG
|
||||
// Sanity check
|
||||
RCTAssert([module class] == _moduleClass, @"Attempted to invoke method \
|
||||
%@ on a module of class %@", _methodName, [module class]);
|
||||
|
||||
// Sanity check
|
||||
RCTAssert([module class] == _moduleClass, @"Attempted to invoke method \
|
||||
%@ on a module of class %@", _methodName, [module class]);
|
||||
#endif
|
||||
|
||||
// Safety check
|
||||
if (arguments.count != _argumentBlocks.count) {
|
||||
RCTLogError(@"%@.%@ was called with %zd arguments, but expects %zd",
|
||||
RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName,
|
||||
arguments.count, _argumentBlocks.count);
|
||||
return;
|
||||
// Safety check
|
||||
if (arguments.count != _argumentBlocks.count) {
|
||||
RCTLogError(@"%@.%@ was called with %zd arguments, but expects %zd",
|
||||
RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName,
|
||||
arguments.count, _argumentBlocks.count);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Create invocation (we can't re-use this as it wouldn't be thread-safe)
|
||||
@ -503,8 +477,8 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
|
||||
NSUInteger index = 0;
|
||||
for (id json in arguments) {
|
||||
id arg = (json == [NSNull null]) ? nil : json;
|
||||
void (^block)(RCTBridge *, NSInvocation *, NSUInteger, id) = _argumentBlocks[index];
|
||||
block(bridge, invocation, index + 2, arg);
|
||||
void (^block)(RCTBridge *, NSNumber *, NSInvocation *, NSUInteger, id) = _argumentBlocks[index];
|
||||
block(bridge, context, invocation, index + 2, arg);
|
||||
index++;
|
||||
}
|
||||
|
||||
@ -653,7 +627,6 @@ static NSDictionary *RCTRemoteModulesConfig(NSDictionary *modulesByName)
|
||||
return moduleConfig;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* As above, but for local modules/methods, which represent JS classes
|
||||
* and methods that will be called by the native code via the bridge.
|
||||
@ -724,7 +697,7 @@ static NSDictionary *RCTLocalModulesConfig()
|
||||
|
||||
@interface RCTDisplayLink : NSObject <RCTInvalidating>
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge selector:(SEL)selector NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
|
||||
@ -738,14 +711,16 @@ static NSDictionary *RCTLocalModulesConfig()
|
||||
{
|
||||
__weak RCTBridge *_bridge;
|
||||
CADisplayLink *_displayLink;
|
||||
SEL _selector;
|
||||
}
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge selector:(SEL)selector
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_bridge = bridge;
|
||||
_selector = selector;
|
||||
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_update:)];
|
||||
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
|
||||
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@ -765,7 +740,10 @@ static NSDictionary *RCTLocalModulesConfig()
|
||||
|
||||
- (void)_update:(CADisplayLink *)displayLink
|
||||
{
|
||||
[_bridge _update:displayLink];
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
|
||||
[_bridge performSelector:_selector withObject:displayLink];
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
|
||||
@end
|
||||
@ -792,20 +770,19 @@ static NSDictionary *RCTLocalModulesConfig()
|
||||
@implementation RCTBridge
|
||||
{
|
||||
RCTSparseArray *_modulesByID;
|
||||
RCTSparseArray *_queuesByID;
|
||||
dispatch_queue_t _methodQueue;
|
||||
NSDictionary *_modulesByName;
|
||||
id<RCTJavaScriptExecutor> _javaScriptExecutor;
|
||||
Class _executorClass;
|
||||
NSURL *_bundleURL;
|
||||
RCTBridgeModuleProviderBlock _moduleProvider;
|
||||
RCTDisplayLink *_displayLink;
|
||||
RCTDisplayLink *_vsyncDisplayLink;
|
||||
NSMutableSet *_frameUpdateObservers;
|
||||
NSMutableArray *_scheduledCalls;
|
||||
NSMutableArray *_scheduledCallbacks;
|
||||
RCTSparseArray *_scheduledCallbacks;
|
||||
BOOL _loading;
|
||||
|
||||
NSUInteger _startingTime;
|
||||
NSMutableArray *_profile;
|
||||
NSLock *_profileLock;
|
||||
}
|
||||
|
||||
static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
@ -821,20 +798,24 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
[self setUp];
|
||||
[self bindKeys];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
Class executorClass = _executorClass ?: _globalExecutorClass ?: [RCTContextExecutor class];
|
||||
_javaScriptExecutor = [[executorClass alloc] init];
|
||||
_javaScriptExecutor = RCTCreateExecutor(executorClass);
|
||||
_latestJSExecutor = _javaScriptExecutor;
|
||||
_eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self];
|
||||
_shadowQueue = dispatch_queue_create("com.facebook.React.ShadowQueue", DISPATCH_QUEUE_SERIAL);
|
||||
_displayLink = [[RCTDisplayLink alloc] initWithBridge:self];
|
||||
_methodQueue = dispatch_queue_create("com.facebook.React.BridgeMethodQueue", DISPATCH_QUEUE_SERIAL);
|
||||
_frameUpdateObservers = [[NSMutableSet alloc] init];
|
||||
_scheduledCalls = [[NSMutableArray alloc] init];
|
||||
_scheduledCallbacks = [[NSMutableArray alloc] init];
|
||||
_scheduledCallbacks = [[RCTSparseArray alloc] init];
|
||||
|
||||
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
|
||||
_displayLink = [[RCTDisplayLink alloc] initWithBridge:self selector:@selector(_jsThreadUpdate:)];
|
||||
}];
|
||||
_vsyncDisplayLink = [[RCTDisplayLink alloc] initWithBridge:self selector:@selector(_mainThreadUpdate:)];
|
||||
|
||||
// Register passed-in module instances
|
||||
NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init];
|
||||
@ -848,21 +829,29 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
[RCTBridgeModuleClassesByModuleID() enumerateObjectsUsingBlock:^(Class moduleClass, NSUInteger moduleID, BOOL *stop) {
|
||||
NSString *moduleName = RCTModuleNamesByID[moduleID];
|
||||
// Check if module instance has already been registered for this name
|
||||
if ((_modulesByID[moduleID] = modulesByName[moduleName])) {
|
||||
id<RCTBridgeModule> module = modulesByName[moduleName];
|
||||
if (module) {
|
||||
// Preregistered instances takes precedence, no questions asked
|
||||
if (!preregisteredModules[moduleName]) {
|
||||
// It's OK to have a name collision as long as the second instance is nil
|
||||
RCTAssert([[moduleClass alloc] init] == nil,
|
||||
@"Attempted to register RCTBridgeModule class %@ for the name '%@', \
|
||||
but name was already registered by class %@", moduleClass,
|
||||
@"Attempted to register RCTBridgeModule class %@ for the name "
|
||||
"'%@', but name was already registered by class %@", moduleClass,
|
||||
moduleName, [modulesByName[moduleName] class]);
|
||||
}
|
||||
if ([module class] != moduleClass) {
|
||||
RCTLogInfo(@"RCTBridgeModule of class %@ with name '%@' was encountered "
|
||||
"in the project, but name was already registered by class %@."
|
||||
"That's fine if it's intentional - just letting you know.",
|
||||
moduleClass, moduleName, [modulesByName[moduleName] class]);
|
||||
}
|
||||
} else {
|
||||
// Module name hasn't been used before, so go ahead and instantiate
|
||||
id<RCTBridgeModule> module = [[moduleClass alloc] init];
|
||||
if (module) {
|
||||
_modulesByID[moduleID] = modulesByName[moduleName] = module;
|
||||
}
|
||||
module = [[moduleClass alloc] init];
|
||||
}
|
||||
if (module) {
|
||||
// Store module instance
|
||||
_modulesByID[moduleID] = modulesByName[moduleName] = module;
|
||||
}
|
||||
}];
|
||||
|
||||
@ -876,6 +865,17 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
}
|
||||
}
|
||||
|
||||
// Get method queues
|
||||
_queuesByID = [[RCTSparseArray alloc] init];
|
||||
[_modulesByID enumerateObjectsUsingBlock:^(id<RCTBridgeModule> module, NSNumber *moduleID, BOOL *stop) {
|
||||
if ([module respondsToSelector:@selector(methodQueue)]) {
|
||||
dispatch_queue_t queue = [module methodQueue];
|
||||
if (queue) {
|
||||
_queuesByID[moduleID] = queue;
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
// Inject module data into JS context
|
||||
NSString *configJSON = RCTJSONStringify(@{
|
||||
@"remoteModuleConfig": RCTRemoteModulesConfig(_modulesByName),
|
||||
@ -902,6 +902,9 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
[loader loadBundleAtURL:_bundleURL onComplete:^(NSError *error) {
|
||||
_loading = NO;
|
||||
if (error != nil) {
|
||||
|
||||
#if RCT_DEBUG // Red box is only available in debug mode
|
||||
|
||||
NSArray *stack = [[error userInfo] objectForKey:@"stack"];
|
||||
if (stack) {
|
||||
[[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription]
|
||||
@ -910,14 +913,13 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
[[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription]
|
||||
withDetails:[error localizedFailureReason]];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} else {
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification
|
||||
object:self];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(reload)
|
||||
name:RCTReloadNotification
|
||||
object:nil];
|
||||
}
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(reload)
|
||||
@ -955,6 +957,9 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
strongSelf.executorClass = Nil;
|
||||
[strongSelf reload];
|
||||
}];
|
||||
|
||||
#if RCT_DEV // Debug executors are only available in dev mode
|
||||
|
||||
// reload in debug mode
|
||||
[commands registerKeyCommandWithInput:@"d"
|
||||
modifierFlags:UIKeyModifierCommand
|
||||
@ -971,10 +976,10 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
[strongSelf reload];
|
||||
}];
|
||||
#endif
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
|
||||
- (NSDictionary *)modules
|
||||
{
|
||||
RCTAssert(_modulesByName != nil, @"Bridge modules have not yet been initialized. "
|
||||
@ -1016,6 +1021,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
_javaScriptExecutor = nil;
|
||||
|
||||
[_displayLink invalidate];
|
||||
[_vsyncDisplayLink invalidate];
|
||||
_frameUpdateObservers = nil;
|
||||
|
||||
// Invalidate modules
|
||||
@ -1027,6 +1033,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
|
||||
// Release modules (breaks retain cycle if module has strong bridge reference)
|
||||
_modulesByID = nil;
|
||||
_queuesByID = nil;
|
||||
_modulesByName = nil;
|
||||
}
|
||||
|
||||
@ -1054,7 +1061,8 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
if (!_loading) {
|
||||
[self _invokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"callFunctionReturnFlushedQueue"
|
||||
arguments:@[moduleID, methodID, args ?: @[]]];
|
||||
arguments:@[moduleID, methodID, args ?: @[]]
|
||||
context:RCTGetExecutorID(_javaScriptExecutor)];
|
||||
}
|
||||
}
|
||||
|
||||
@ -1075,13 +1083,15 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
#if BATCHED_BRIDGE
|
||||
[self _actuallyInvokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"callFunctionReturnFlushedQueue"
|
||||
arguments:@[moduleID, methodID, @[@[timer]]]];
|
||||
arguments:@[moduleID, methodID, @[@[timer]]]
|
||||
context:RCTGetExecutorID(_javaScriptExecutor)];
|
||||
|
||||
#else
|
||||
|
||||
[self _invokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"callFunctionReturnFlushedQueue"
|
||||
arguments:@[moduleID, methodID, @[@[timer]]]];
|
||||
arguments:@[moduleID, methodID, @[@[timer]]]
|
||||
context:RCTGetExecutorID(_javaScriptExecutor)];
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@ -1089,23 +1099,28 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
- (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete
|
||||
{
|
||||
RCTAssert(onComplete != nil, @"onComplete block passed in should be non-nil");
|
||||
RCT_PROFILE_START();
|
||||
RCTProfileBeginEvent();
|
||||
[_javaScriptExecutor executeApplicationScript:script sourceURL:url onComplete:^(NSError *scriptLoadError) {
|
||||
RCT_PROFILE_END(js_call, scriptLoadError, @"initial_script");
|
||||
RCTProfileEndEvent(@"ApplicationScript", @"js_call,init", scriptLoadError);
|
||||
if (scriptLoadError) {
|
||||
onComplete(scriptLoadError);
|
||||
return;
|
||||
}
|
||||
|
||||
RCT_PROFILE_START();
|
||||
RCTProfileBeginEvent();
|
||||
NSNumber *context = RCTGetExecutorID(_javaScriptExecutor);
|
||||
[_javaScriptExecutor executeJSCall:@"BatchedBridge"
|
||||
method:@"flushedQueue"
|
||||
arguments:@[]
|
||||
context:context
|
||||
callback:^(id json, NSError *error) {
|
||||
RCT_PROFILE_END(js_call, error, @"initial_call", @"BatchedBridge.flushedQueue");
|
||||
RCT_PROFILE_START();
|
||||
[self _handleBuffer:json];
|
||||
RCT_PROFILE_END(objc_call, json, @"batched_js_calls");
|
||||
RCTProfileEndEvent(@"FetchApplicationScriptCallbacks", @"js_call,init", @{
|
||||
@"json": json ?: [NSNull null],
|
||||
@"error": error ?: [NSNull null],
|
||||
});
|
||||
|
||||
[self _handleBuffer:json context:context];
|
||||
|
||||
onComplete(error);
|
||||
}];
|
||||
}];
|
||||
@ -1113,10 +1128,10 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
|
||||
#pragma mark - Payload Generation
|
||||
|
||||
- (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args
|
||||
- (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args context:(NSNumber *)context
|
||||
{
|
||||
#if BATCHED_BRIDGE
|
||||
RCT_PROFILE_START();
|
||||
RCTProfileBeginEvent();
|
||||
|
||||
if ([module isEqualToString:@"RCTEventEmitter"]) {
|
||||
for (NSDictionary *call in _scheduledCalls) {
|
||||
@ -1130,55 +1145,55 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
@"module": module,
|
||||
@"method": method,
|
||||
@"args": args,
|
||||
@"context": context ?: @0,
|
||||
};
|
||||
|
||||
if ([method isEqualToString:@"invokeCallbackAndReturnFlushedQueue"]) {
|
||||
[_scheduledCallbacks addObject:call];
|
||||
_scheduledCallbacks[args[0]] = call;
|
||||
} else {
|
||||
[_scheduledCalls addObject:call];
|
||||
}
|
||||
|
||||
RCT_PROFILE_END(js_call, args, @"schedule", module, method);
|
||||
RCTProfileEndEvent(@"enqueue_call", @"objc_call", call);
|
||||
}
|
||||
|
||||
- (void)_actuallyInvokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args
|
||||
- (void)_actuallyInvokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args context:(NSNumber *)context
|
||||
{
|
||||
#endif
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTEnqueueNotification object:nil userInfo:nil];
|
||||
|
||||
NSString *moduleDotMethod = [NSString stringWithFormat:@"%@.%@", module, method];
|
||||
RCT_PROFILE_START();
|
||||
RCTJavaScriptCallback processResponse = ^(id json, NSError *error) {
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTDequeueNotification object:nil userInfo:nil];
|
||||
RCT_PROFILE_END(js_call, args, moduleDotMethod);
|
||||
|
||||
RCT_PROFILE_START();
|
||||
[self _handleBuffer:json];
|
||||
RCT_PROFILE_END(objc_call, json, @"batched_js_calls");
|
||||
[self _handleBuffer:json context:context];
|
||||
};
|
||||
|
||||
[_javaScriptExecutor executeJSCall:module
|
||||
method:method
|
||||
arguments:args
|
||||
context:context
|
||||
callback:processResponse];
|
||||
}
|
||||
|
||||
#pragma mark - Payload Processing
|
||||
|
||||
- (void)_handleBuffer:(id)buffer
|
||||
- (void)_handleBuffer:(id)buffer context:(NSNumber *)context
|
||||
{
|
||||
if (buffer == nil || buffer == (id)kCFNull) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSArray *requestsArray = [RCTConvert NSArray:buffer];
|
||||
|
||||
#if RCT_DEBUG
|
||||
|
||||
if (![buffer isKindOfClass:[NSArray class]]) {
|
||||
RCTLogError(@"Buffer must be an instance of NSArray, got %@", NSStringFromClass([buffer class]));
|
||||
return;
|
||||
}
|
||||
|
||||
NSArray *requestsArray = (NSArray *)buffer;
|
||||
NSUInteger bufferRowCount = [requestsArray count];
|
||||
NSUInteger expectedFieldsCount = RCTBridgeFieldResponseReturnValues + 1;
|
||||
|
||||
if (bufferRowCount != expectedFieldsCount) {
|
||||
RCTLogError(@"Must pass all fields to buffer - expected %zd, saw %zd", expectedFieldsCount, bufferRowCount);
|
||||
return;
|
||||
@ -1192,56 +1207,75 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
NSArray *moduleIDs = requestsArray[RCTBridgeFieldRequestModuleIDs];
|
||||
NSArray *methodIDs = requestsArray[RCTBridgeFieldMethodIDs];
|
||||
NSArray *paramsArrays = requestsArray[RCTBridgeFieldParamss];
|
||||
|
||||
NSUInteger numRequests = [moduleIDs count];
|
||||
BOOL allSame = numRequests == [methodIDs count] && numRequests == [paramsArrays count];
|
||||
if (!allSame) {
|
||||
|
||||
if (RCT_DEBUG && (numRequests != methodIDs.count || numRequests != paramsArrays.count)) {
|
||||
RCTLogError(@"Invalid data message - all must be length: %zd", numRequests);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: if we sort the requests by module, we could dispatch once per
|
||||
// module instead of per request, which would reduce the call overhead.
|
||||
for (NSUInteger i = 0; i < numRequests; i++) {
|
||||
@autoreleasepool {
|
||||
[self _handleRequestNumber:i
|
||||
moduleID:[moduleIDs[i] integerValue]
|
||||
methodID:[methodIDs[i] integerValue]
|
||||
params:paramsArrays[i]];
|
||||
params:paramsArrays[i]
|
||||
context:context];
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: only used by RCTUIManager - can we eliminate this special case?
|
||||
dispatch_async(self.shadowQueue, ^{
|
||||
for (id module in _modulesByID.allObjects) {
|
||||
if ([module respondsToSelector:@selector(batchDidComplete)]) {
|
||||
// TODO: batchDidComplete is only used by RCTUIManager - can we eliminate this special case?
|
||||
[_modulesByID enumerateObjectsUsingBlock:^(id<RCTBridgeModule> module, NSNumber *moduleID, BOOL *stop) {
|
||||
if ([module respondsToSelector:@selector(batchDidComplete)]) {
|
||||
dispatch_queue_t queue = _queuesByID[moduleID];
|
||||
dispatch_async(queue ?: _methodQueue, ^{
|
||||
[module batchDidComplete];
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
- (BOOL)_handleRequestNumber:(NSUInteger)i
|
||||
moduleID:(NSUInteger)moduleID
|
||||
methodID:(NSUInteger)methodID
|
||||
params:(NSArray *)params
|
||||
context:(NSNumber *)context
|
||||
{
|
||||
if (![params isKindOfClass:[NSArray class]]) {
|
||||
|
||||
if (RCT_DEBUG && ![params isKindOfClass:[NSArray class]]) {
|
||||
RCTLogError(@"Invalid module/method/params tuple for request #%zd", i);
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Look up method
|
||||
NSArray *methods = RCTExportedMethodsByModuleID()[moduleID];
|
||||
if (methodID >= methods.count) {
|
||||
|
||||
if (RCT_DEBUG && methodID >= methods.count) {
|
||||
RCTLogError(@"Unknown methodID: %zd for module: %zd (%@)", methodID, moduleID, RCTModuleNamesByID[moduleID]);
|
||||
return NO;
|
||||
}
|
||||
|
||||
RCTModuleMethod *method = methods[methodID];
|
||||
|
||||
// Look up module
|
||||
id module = self->_modulesByID[moduleID];
|
||||
if (RCT_DEBUG && !module) {
|
||||
RCTLogError(@"No module found for name '%@'", RCTModuleNamesByID[moduleID]);
|
||||
return NO;
|
||||
}
|
||||
|
||||
__weak RCTBridge *weakSelf = self;
|
||||
dispatch_async(self.shadowQueue, ^{
|
||||
dispatch_queue_t queue = _queuesByID[moduleID];
|
||||
dispatch_async(queue ?: _methodQueue, ^{
|
||||
RCTProfileBeginEvent();
|
||||
__strong RCTBridge *strongSelf = weakSelf;
|
||||
|
||||
if (!strongSelf.isValid) {
|
||||
@ -1250,30 +1284,34 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
return;
|
||||
}
|
||||
|
||||
// Look up module
|
||||
id module = strongSelf->_modulesByID[moduleID];
|
||||
if (!module) {
|
||||
RCTLogError(@"No module found for name '%@'", RCTModuleNamesByID[moduleID]);
|
||||
return;
|
||||
}
|
||||
|
||||
@try {
|
||||
[method invokeWithBridge:strongSelf module:module arguments:params];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
RCTLogError(@"Exception thrown while invoking %@ on target %@ with params %@: %@", method.JSMethodName, module, params, exception);
|
||||
if ([exception.name rangeOfString:@"Unhandled JS Exception"].location != NSNotFound) {
|
||||
@throw;
|
||||
if (!RCT_DEBUG) {
|
||||
[method invokeWithBridge:strongSelf module:module arguments:params context:context];
|
||||
} else {
|
||||
@try {
|
||||
[method invokeWithBridge:strongSelf module:module arguments:params context:context];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
RCTLogError(@"Exception thrown while invoking %@ on target %@ with params %@: %@", method.JSMethodName, module, params, exception);
|
||||
if ([exception.name rangeOfString:@"Unhandled JS Exception"].location != NSNotFound) {
|
||||
@throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RCTProfileEndEvent(@"Invoke callback", @"objc_call", @{
|
||||
@"module": method.moduleClassName,
|
||||
@"method": method.JSMethodName,
|
||||
@"selector": NSStringFromSelector(method.selector),
|
||||
});
|
||||
});
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)_update:(CADisplayLink *)displayLink
|
||||
- (void)_jsThreadUpdate:(CADisplayLink *)displayLink
|
||||
{
|
||||
RCT_PROFILE_START();
|
||||
RCTProfileImmediateEvent(@"JS Thread Tick", displayLink.timestamp, @"g");
|
||||
RCTProfileBeginEvent();
|
||||
|
||||
RCTFrameUpdate *frameUpdate = [[RCTFrameUpdate alloc] initWithDisplayLink:displayLink];
|
||||
for (id<RCTFrameUpdateObserver> observer in _frameUpdateObservers) {
|
||||
@ -1282,25 +1320,30 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
}
|
||||
}
|
||||
|
||||
[self _runScheduledCalls];
|
||||
|
||||
RCT_PROFILE_END(display_link, nil, @"main_thread");
|
||||
}
|
||||
|
||||
- (void)_runScheduledCalls
|
||||
{
|
||||
#if BATCHED_BRIDGE
|
||||
|
||||
NSArray *calls = [_scheduledCallbacks arrayByAddingObjectsFromArray:_scheduledCalls];
|
||||
NSArray *calls = [_scheduledCallbacks.allObjects arrayByAddingObjectsFromArray:_scheduledCalls];
|
||||
NSNumber *currentExecutorID = RCTGetExecutorID(_javaScriptExecutor);
|
||||
calls = [calls filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSDictionary *call, NSDictionary *bindings) {
|
||||
return [call[@"context"] isEqualToNumber:currentExecutorID];
|
||||
}]];
|
||||
if (calls.count > 0) {
|
||||
_scheduledCalls = [[NSMutableArray alloc] init];
|
||||
_scheduledCallbacks = [[NSMutableArray alloc] init];
|
||||
_scheduledCallbacks = [[RCTSparseArray alloc] init];
|
||||
[self _actuallyInvokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"processBatch"
|
||||
arguments:@[calls]];
|
||||
method:@"processBatch"
|
||||
arguments:@[calls]
|
||||
context:RCTGetExecutorID(_javaScriptExecutor)];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
RCTProfileEndEvent(@"DispatchFrameUpdate", @"objc_call", nil);
|
||||
}
|
||||
|
||||
- (void)_mainThreadUpdate:(CADisplayLink *)displayLink
|
||||
{
|
||||
RCTProfileImmediateEvent(@"VSYNC", displayLink.timestamp, @"g");
|
||||
}
|
||||
|
||||
- (void)addFrameUpdateObserver:(id<RCTFrameUpdateObserver>)observer
|
||||
@ -1315,13 +1358,15 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
|
||||
- (void)reload
|
||||
{
|
||||
if (!_loading) {
|
||||
// If the bridge has not loaded yet, the context will be already invalid at
|
||||
// the time the javascript gets executed.
|
||||
// It will crash the javascript, and even the next `load` won't render.
|
||||
[self invalidate];
|
||||
[self setUp];
|
||||
}
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (!_loading) {
|
||||
// If the bridge has not loaded yet, the context will be already invalid at
|
||||
// the time the javascript gets executed.
|
||||
// It will crash the javascript, and even the next `load` won't render.
|
||||
[self invalidate];
|
||||
[self setUp];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+ (void)logMessage:(NSString *)message level:(NSString *)level
|
||||
@ -1335,6 +1380,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
[_latestJSExecutor executeJSCall:@"RCTLog"
|
||||
method:@"logIfNoNativeHook"
|
||||
arguments:@[level, message]
|
||||
context:RCTGetExecutorID(_latestJSExecutor)
|
||||
callback:^(id json, NSError *error) {}];
|
||||
}
|
||||
|
||||
@ -1344,23 +1390,12 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
RCTLogError(@"To run the profiler you must be running from the dev server");
|
||||
return;
|
||||
}
|
||||
_profileLock = [[NSLock alloc] init];
|
||||
_startingTime = CACurrentMediaTime();
|
||||
|
||||
[_profileLock lock];
|
||||
_profile = [[NSMutableArray alloc] init];
|
||||
[_profileLock unlock];
|
||||
RCTProfileInit();
|
||||
}
|
||||
|
||||
- (void)stopProfiling
|
||||
{
|
||||
[_profileLock lock];
|
||||
NSArray *profile = _profile;
|
||||
_profile = nil;
|
||||
[_profileLock unlock];
|
||||
_profileLock = nil;
|
||||
|
||||
NSString *log = RCTJSONStringify(profile, NULL);
|
||||
NSString *log = RCTProfileEnd();
|
||||
NSString *URLString = [NSString stringWithFormat:@"%@://%@:%@/profile", _bundleURL.scheme, _bundleURL.host, _bundleURL.port];
|
||||
NSURL *URL = [NSURL URLWithString:URLString];
|
||||
NSMutableURLRequest *URLRequest = [NSMutableURLRequest requestWithURL:URL];
|
||||
|
@ -73,12 +73,67 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response);
|
||||
* { ... }
|
||||
*/
|
||||
#define RCT_REMAP_METHOD(js_name, method) \
|
||||
RCT_EXTERN_REMAP_METHOD(js_name, method) \
|
||||
- (void)method
|
||||
|
||||
/**
|
||||
* Use this macro in a private Objective-C implementation file to automatically
|
||||
* register an external module with the bridge when it loads. This allows you to
|
||||
* register Swift or private Objective-C classes with the bridge.
|
||||
*
|
||||
* For example if one wanted to export a Swift class to the bridge:
|
||||
*
|
||||
* MyModule.swift:
|
||||
*
|
||||
* @objc(MyModule) class MyModule: NSObject {
|
||||
*
|
||||
* @objc func doSomething(string: String! withFoo a: Int, bar b: Int) { ... }
|
||||
*
|
||||
* }
|
||||
*
|
||||
* MyModuleExport.m:
|
||||
*
|
||||
* #import "RCTBridgeModule.h"
|
||||
*
|
||||
* @interface RCT_EXTERN_MODULE(MyModule, NSObject)
|
||||
*
|
||||
* RCT_EXTERN_METHOD(doSomething:(NSString *)string withFoo:(NSInteger)a bar:(NSInteger)b)
|
||||
*
|
||||
* @end
|
||||
*
|
||||
* This will now expose MyModule and the method to JavaScript via
|
||||
* `NativeModules.MyModule.doSomething`
|
||||
*/
|
||||
#define RCT_EXTERN_MODULE(objc_name, objc_supername) \
|
||||
RCT_EXTERN_REMAP_MODULE(, objc_name, objc_supername)
|
||||
|
||||
/**
|
||||
* Similar to RCT_EXTERN_MODULE but allows setting a custom JavaScript name
|
||||
*/
|
||||
#define RCT_EXTERN_REMAP_MODULE(js_name, objc_name, objc_supername) \
|
||||
objc_name : objc_supername \
|
||||
@end \
|
||||
@interface objc_name (RCTExternModule) <RCTBridgeModule> \
|
||||
@end \
|
||||
@implementation objc_name (RCTExternModule) \
|
||||
RCT_EXPORT_MODULE(js_name)
|
||||
|
||||
/**
|
||||
* Use this macro in accordance with RCT_EXTERN_MODULE to export methods
|
||||
* of an external module.
|
||||
*/
|
||||
#define RCT_EXTERN_METHOD(method) \
|
||||
RCT_EXTERN_REMAP_METHOD(, method)
|
||||
|
||||
/**
|
||||
* Similar to RCT_EXTERN_REMAP_METHOD but allows setting a custom JavaScript name
|
||||
*/
|
||||
#define RCT_EXTERN_REMAP_METHOD(js_name, method) \
|
||||
- (void)__rct_export__##method { \
|
||||
__attribute__((used, section("__DATA,RCTExport"))) \
|
||||
__attribute__((__aligned__(1))) \
|
||||
static const char *__rct_export_entry__[] = { __func__, #method, #js_name }; \
|
||||
} \
|
||||
- (void)method
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated, do not use.
|
||||
@ -89,6 +144,37 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response);
|
||||
__attribute__((__aligned__(1))) \
|
||||
static const char *__rct_export_entry__[] = { __func__, #js_name, NULL }
|
||||
|
||||
/**
|
||||
* The queue that will be used to call all exported methods. If omitted, this
|
||||
* will call on the default background queue, which is avoids blocking the main
|
||||
* thread.
|
||||
*
|
||||
* If the methods in your module need to interact with UIKit methods, they will
|
||||
* probably need to call those on the main thread, as most of UIKit is main-
|
||||
* thread-only. You can tell React Native to call your module methods on the
|
||||
* main thread by returning a reference to the main queue, like this:
|
||||
*
|
||||
* - (dispatch_queue_t)methodQueue
|
||||
* {
|
||||
* return dispatch_get_main_queue();
|
||||
* }
|
||||
*
|
||||
* If your methods perform heavy work such as synchronous filesystem or network
|
||||
* access, you probably don't want to block the default background queue, as
|
||||
* this will stall other methods. Instead, you should return a custom serial
|
||||
* queue, like this:
|
||||
*
|
||||
* - (dispatch_queue_t)methodQueue
|
||||
* {
|
||||
* return dispatch_queue_create("com.mydomain.FileQueue", DISPATCH_QUEUE_SERIAL);
|
||||
* }
|
||||
*
|
||||
* Alternatively, if only some methods of the module should be executed on a
|
||||
* particular queue you can leave this method unimplemented, and simply
|
||||
* dispatch_async() to the required queue within the method itself.
|
||||
*/
|
||||
- (dispatch_queue_t)methodQueue;
|
||||
|
||||
/**
|
||||
* Injects constants into JS. These constants are made accessible via
|
||||
* NativeModules.ModuleName.X. This method is called when the module is
|
||||
|
@ -7,16 +7,14 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <objc/message.h>
|
||||
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "../Layout/Layout.h"
|
||||
#import "../Views/RCTAnimationType.h"
|
||||
#import "../Views/RCTPointerEvents.h"
|
||||
|
||||
#import "Layout.h"
|
||||
#import "RCTAnimationType.h"
|
||||
#import "RCTDefines.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTPointerEvents.h"
|
||||
|
||||
/**
|
||||
* This class provides a collection of conversion functions for mapping
|
||||
@ -116,33 +114,27 @@ typedef BOOL css_overflow;
|
||||
|
||||
@end
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* This function will attempt to set a property using a json value by first
|
||||
* inferring the correct type from all available information, and then
|
||||
* applying an appropriate conversion method. If the property does not
|
||||
* exist, or the type cannot be inferred, the function will return NO.
|
||||
*/
|
||||
BOOL RCTSetProperty(id target, NSString *keyPath, SEL type, id json);
|
||||
RCT_EXTERN BOOL RCTSetProperty(id target, NSString *keyPath, SEL type, id json);
|
||||
|
||||
/**
|
||||
* This function attempts to copy a property from the source object to the
|
||||
* destination object using KVC. If the property does not exist, or cannot
|
||||
* be set, it will do nothing and return NO.
|
||||
*/
|
||||
BOOL RCTCopyProperty(id target, id source, NSString *keyPath);
|
||||
RCT_EXTERN BOOL RCTCopyProperty(id target, id source, NSString *keyPath);
|
||||
|
||||
/**
|
||||
* Underlying implementation of RCT_ENUM_CONVERTER macro. Ignore this.
|
||||
* Underlying implementations of RCT_XXX_CONVERTER macros. Ignore these.
|
||||
*/
|
||||
NSNumber *RCTConverterEnumValue(const char *, NSDictionary *, NSNumber *, id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
RCT_EXTERN NSNumber *RCTConvertEnumValue(const char *, NSDictionary *, NSNumber *, id);
|
||||
RCT_EXTERN NSArray *RCTConvertArrayValue(SEL, id);
|
||||
RCT_EXTERN void RCTLogConvertError(id, const char *);
|
||||
|
||||
/**
|
||||
* This macro is used for creating simple converter functions that just call
|
||||
@ -157,18 +149,19 @@ RCT_CUSTOM_CONVERTER(type, name, [json getter])
|
||||
#define RCT_CUSTOM_CONVERTER(type, name, code) \
|
||||
+ (type)name:(id)json \
|
||||
{ \
|
||||
if (json == [NSNull null]) { \
|
||||
json = nil; \
|
||||
} \
|
||||
@try { \
|
||||
json = (json == (id)kCFNull) ? nil : json; \
|
||||
if (!RCT_DEBUG) { \
|
||||
return code; \
|
||||
} else { \
|
||||
@try { \
|
||||
return code; \
|
||||
} \
|
||||
@catch (__unused NSException *e) { \
|
||||
RCTLogConvertError(json, #type); \
|
||||
json = nil; \
|
||||
return code; \
|
||||
} \
|
||||
} \
|
||||
@catch (__unused NSException *e) { \
|
||||
RCTLogError(@"JSON value '%@' of type '%@' cannot be converted to '%s'", \
|
||||
json, [json classForCoder], #type); \
|
||||
json = nil; \
|
||||
return code; \
|
||||
} \
|
||||
}
|
||||
|
||||
/**
|
||||
@ -190,22 +183,14 @@ RCT_CUSTOM_CONVERTER(type, type, [[self NSNumber:json] getter])
|
||||
dispatch_once(&onceToken, ^{ \
|
||||
mapping = values; \
|
||||
}); \
|
||||
NSNumber *converted = RCTConverterEnumValue(#type, mapping, @(default), json); \
|
||||
return ((type(*)(id, SEL))objc_msgSend)(converted, @selector(getter)); \
|
||||
return [RCTConvertEnumValue(#type, mapping, @(default), json) getter]; \
|
||||
}
|
||||
|
||||
/**
|
||||
* This macro is used for creating converter functions for typed arrays.
|
||||
*/
|
||||
#define RCT_ARRAY_CONVERTER(type) \
|
||||
+ (type##Array *)type##Array:(id)json \
|
||||
{ \
|
||||
NSMutableArray *values = [[NSMutableArray alloc] init]; \
|
||||
for (id jsonValue in [self NSArray:json]) { \
|
||||
id value = [self type:jsonValue]; \
|
||||
if (value) { \
|
||||
[values addObject:value]; \
|
||||
} \
|
||||
} \
|
||||
return values; \
|
||||
#define RCT_ARRAY_CONVERTER(type) \
|
||||
+ (NSArray *)type##Array:(id)json \
|
||||
{ \
|
||||
return RCTConvertArrayValue(@selector(type:), json); \
|
||||
}
|
||||
|
@ -11,8 +11,16 @@
|
||||
|
||||
#import <objc/message.h>
|
||||
|
||||
#import "RCTDefines.h"
|
||||
|
||||
@implementation RCTConvert
|
||||
|
||||
void RCTLogConvertError(id json, const char *type)
|
||||
{
|
||||
RCTLogError(@"JSON value '%@' of type '%@' cannot be converted to %s",
|
||||
json, [json classForCoder], type);
|
||||
}
|
||||
|
||||
RCT_CONVERTER(BOOL, BOOL, boolValue)
|
||||
RCT_NUMBER_CONVERTER(double, doubleValue)
|
||||
RCT_NUMBER_CONVERTER(float, floatValue)
|
||||
@ -58,6 +66,10 @@ RCT_CONVERTER(NSString *, NSString, description)
|
||||
|
||||
+ (NSURL *)NSURL:(id)json
|
||||
{
|
||||
if (!json || json == (id)kCFNull) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (![json isKindOfClass:[NSString class]]) {
|
||||
RCTLogError(@"Expected NSString for NSURL, received %@: %@", [json classForCoder], json);
|
||||
return nil;
|
||||
@ -115,7 +127,7 @@ RCT_CUSTOM_CONVERTER(NSTimeInterval, NSTimeInterval, [self double:json] / 1000.0
|
||||
// JS standard for time zones is minutes.
|
||||
RCT_CUSTOM_CONVERTER(NSTimeZone *, NSTimeZone, [NSTimeZone timeZoneForSecondsFromGMT:[self double:json] * 60.0])
|
||||
|
||||
NSNumber *RCTConverterEnumValue(const char *typeName, NSDictionary *mapping, NSNumber *defaultValue, id json)
|
||||
NSNumber *RCTConvertEnumValue(const char *typeName, NSDictionary *mapping, NSNumber *defaultValue, id json)
|
||||
{
|
||||
if (!json || json == (id)kCFNull) {
|
||||
return defaultValue;
|
||||
@ -224,57 +236,52 @@ RCT_ENUM_CONVERTER(UIBarStyle, (@{
|
||||
}), UIBarStyleDefault, integerValue)
|
||||
|
||||
// TODO: normalise the use of w/width so we can do away with the alias values (#6566645)
|
||||
static void RCTConvertCGStructValue(const char *type, NSArray *fields, NSDictionary *aliases, CGFloat *result, id json)
|
||||
{
|
||||
NSUInteger count = fields.count;
|
||||
if ([json isKindOfClass:[NSArray class]]) {
|
||||
if (RCT_DEBUG && [json count] != count) {
|
||||
RCTLogError(@"Expected array with count %zd, but count is %zd: %@", count, [json count], json);
|
||||
} else {
|
||||
for (NSUInteger i = 0; i < count; i++) {
|
||||
result[i] = [RCTConvert CGFloat:json[i]];
|
||||
}
|
||||
}
|
||||
} else if ([json isKindOfClass:[NSDictionary class]]) {
|
||||
if (aliases.count) {
|
||||
json = [json mutableCopy];
|
||||
for (NSString *alias in aliases) {
|
||||
NSString *key = aliases[alias];
|
||||
NSNumber *number = json[alias];
|
||||
if (number) {
|
||||
RCTLogWarn(@"Using deprecated '%@' property for '%s'. Use '%@' instead.", alias, type, key);
|
||||
((NSMutableDictionary *)json)[key] = number;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (NSUInteger i = 0; i < count; i++) {
|
||||
result[i] = [RCTConvert CGFloat:json[fields[i]]];
|
||||
}
|
||||
} else if (RCT_DEBUG && json && json != (id)kCFNull) {
|
||||
RCTLogConvertError(json, type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This macro is used for creating converter functions for structs that consist
|
||||
* of a number of CGFloat properties, such as CGPoint, CGRect, etc.
|
||||
*/
|
||||
#define RCT_CGSTRUCT_CONVERTER(type, values, _aliases) \
|
||||
+ (type)type:(id)json \
|
||||
{ \
|
||||
@try { \
|
||||
static NSArray *fields; \
|
||||
static NSUInteger count; \
|
||||
static dispatch_once_t onceToken; \
|
||||
dispatch_once(&onceToken, ^{ \
|
||||
fields = values; \
|
||||
count = [fields count]; \
|
||||
}); \
|
||||
type result; \
|
||||
if ([json isKindOfClass:[NSArray class]]) { \
|
||||
if ([json count] != count) { \
|
||||
RCTLogError(@"Expected array with count %zd, but count is %zd: %@", count, [json count], json); \
|
||||
} else { \
|
||||
for (NSUInteger i = 0; i < count; i++) { \
|
||||
((CGFloat *)&result)[i] = [self CGFloat:json[i]]; \
|
||||
} \
|
||||
} \
|
||||
} else if ([json isKindOfClass:[NSDictionary class]]) { \
|
||||
NSDictionary *aliases = _aliases; \
|
||||
if (aliases.count) { \
|
||||
json = [json mutableCopy]; \
|
||||
for (NSString *alias in aliases) { \
|
||||
NSString *key = aliases[alias]; \
|
||||
NSNumber *number = json[alias]; \
|
||||
if (number) { \
|
||||
RCTLogWarn(@"Using deprecated '%@' property for '%s'. Use '%@' instead.", alias, #type, key); \
|
||||
((NSMutableDictionary *)json)[key] = number; \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
for (NSUInteger i = 0; i < count; i++) { \
|
||||
((CGFloat *)&result)[i] = [self CGFloat:json[fields[i]]]; \
|
||||
} \
|
||||
} else if (json && json != [NSNull null]) { \
|
||||
RCTLogError(@"Expected NSArray or NSDictionary for %s, received %@: %@", \
|
||||
#type, [json classForCoder], json); \
|
||||
} \
|
||||
return result; \
|
||||
} \
|
||||
@catch (__unused NSException *e) { \
|
||||
RCTLogError(@"JSON value '%@' cannot be converted to '%s'", json, #type); \
|
||||
type result; \
|
||||
return result; \
|
||||
} \
|
||||
#define RCT_CGSTRUCT_CONVERTER(type, values, aliases) \
|
||||
+ (type)type:(id)json \
|
||||
{ \
|
||||
static NSArray *fields; \
|
||||
static dispatch_once_t onceToken; \
|
||||
dispatch_once(&onceToken, ^{ \
|
||||
fields = values; \
|
||||
}); \
|
||||
type result; \
|
||||
RCTConvertCGStructValue(#type, fields, aliases, (CGFloat *)&result, json); \
|
||||
return result; \
|
||||
}
|
||||
|
||||
RCT_CUSTOM_CONVERTER(CGFloat, CGFloat, [self double:json])
|
||||
@ -521,9 +528,7 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[
|
||||
} else if ([json isKindOfClass:[NSArray class]]) {
|
||||
|
||||
if ([json count] < 3 || [json count] > 4) {
|
||||
|
||||
RCTLogError(@"Expected array with count 3 or 4, but count is %zd: %@", [json count], json);
|
||||
|
||||
} else {
|
||||
|
||||
// Color array
|
||||
@ -541,10 +546,9 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[
|
||||
blue:[self double:json[@"b"]]
|
||||
alpha:[self double:json[@"a"] ?: @1]];
|
||||
|
||||
} else if (json && ![json isKindOfClass:[NSNull class]]) {
|
||||
|
||||
RCTLogError(@"Expected NSArray, NSDictionary or NSString for UIColor, received %@: %@",
|
||||
[json classForCoder], json);
|
||||
}
|
||||
else if (RCT_DEBUG && json && json != (id)kCFNull) {
|
||||
RCTLogConvertError(json, "a color");
|
||||
}
|
||||
|
||||
// Default color
|
||||
@ -569,8 +573,12 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[
|
||||
// TODO: we might as well cache the result of these checks (and possibly the
|
||||
// image itself) so as to reduce overhead on subsequent checks of the same input
|
||||
|
||||
if (![json isKindOfClass:[NSString class]]) {
|
||||
RCTLogError(@"Expected NSString for UIImage, received %@: %@", [json classForCoder], json);
|
||||
if (!json || json == (id)kCFNull) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (RCT_DEBUG && ![json isKindOfClass:[NSString class]]) {
|
||||
RCTLogConvertError(json, "an image");
|
||||
return nil;
|
||||
}
|
||||
|
||||
@ -757,6 +765,29 @@ static BOOL RCTFontIsCondensed(UIFont *font)
|
||||
return bestMatch;
|
||||
}
|
||||
|
||||
NSArray *RCTConvertArrayValue(SEL type, id json)
|
||||
{
|
||||
__block BOOL copy = NO;
|
||||
__block NSArray *values = json = [RCTConvert NSArray:json];
|
||||
[json enumerateObjectsUsingBlock:^(id jsonValue, NSUInteger idx, BOOL *stop) {
|
||||
id value = ((id(*)(Class, SEL, id))objc_msgSend)([RCTConvert class], type, jsonValue);
|
||||
if (copy) {
|
||||
if (value) {
|
||||
[(NSMutableArray *)values addObject:value];
|
||||
}
|
||||
} else if (value != jsonValue) {
|
||||
// Converted value is different, so we'll need to copy the array
|
||||
values = [[NSMutableArray alloc] initWithCapacity:values.count];
|
||||
for (NSInteger i = 0; i < idx; i++) {
|
||||
[(NSMutableArray *)values addObject:json[i]];
|
||||
}
|
||||
[(NSMutableArray *)values addObject:value];
|
||||
copy = YES;
|
||||
}
|
||||
}];
|
||||
return values;
|
||||
}
|
||||
|
||||
RCT_ARRAY_CONVERTER(NSString)
|
||||
RCT_ARRAY_CONVERTER(NSDictionary)
|
||||
RCT_ARRAY_CONVERTER(NSURL)
|
||||
|
55
React/Base/RCTDefines.h
Normal file
55
React/Base/RCTDefines.h
Normal file
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
* Make global functions usable in C++
|
||||
*/
|
||||
#if defined(__cplusplus)
|
||||
#define RCT_EXTERN extern "C" __attribute__((visibility("default")))
|
||||
#else
|
||||
#define RCT_EXTERN extern __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
/**
|
||||
* The RCT_DEBUG macro can be used to exclude error checking and logging code
|
||||
* from release builds to improve performance and reduce binary size.
|
||||
*/
|
||||
#ifndef RCT_DEBUG
|
||||
#if DEBUG
|
||||
#define RCT_DEBUG 1
|
||||
#else
|
||||
#define RCT_DEBUG 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/**
|
||||
* The RCT_DEV macro can be used to enable or disable development tools
|
||||
* such as the debug executors, dev menu, red box, etc.
|
||||
*/
|
||||
#ifndef RCT_DEV
|
||||
#if DEBUG
|
||||
#define RCT_DEV 1
|
||||
#else
|
||||
#define RCT_DEV 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/**
|
||||
* By default, only raise an NSAssertion in debug mode
|
||||
* (custom assert functions will still be called).
|
||||
*/
|
||||
#ifndef RCT_NSASSERT
|
||||
#if RCT_DEBUG
|
||||
#define RCT_NSASSERT 1
|
||||
#else
|
||||
#define RCT_NSASSERT 0
|
||||
#endif
|
||||
#endif
|
@ -9,11 +9,50 @@
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class RCTBridge;
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTBridgeModule.h"
|
||||
#import "RCTInvalidating.h"
|
||||
|
||||
@interface RCTDevMenu : NSObject
|
||||
/**
|
||||
* Developer menu, useful for exposing extra functionality when debugging.
|
||||
*/
|
||||
@interface RCTDevMenu : NSObject <RCTBridgeModule, RCTInvalidating>
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
|
||||
/**
|
||||
* Is the menu enabled. The menu is enabled by default in debug mode, but
|
||||
* you may wish to disable it so that you can provide your own shake handler.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL shakeToShow;
|
||||
|
||||
/**
|
||||
* Enables performance profiling.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL profilingEnabled;
|
||||
|
||||
/**
|
||||
* Enables automatic polling for JS code changes. Only applicable when
|
||||
* running the app from a server.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL liveReloadEnabled;
|
||||
|
||||
/**
|
||||
* The time between checks for code changes. Defaults to 1 second.
|
||||
*/
|
||||
@property (nonatomic, assign) NSTimeInterval liveReloadPeriod;
|
||||
|
||||
/**
|
||||
* Manually show the menu. This will.
|
||||
*/
|
||||
- (void)show;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* This category makes the developer menu instance available via the
|
||||
* RCTBridge, which is useful for any class that needs to access the menu.
|
||||
*/
|
||||
@interface RCTBridge (RCTDevMenu)
|
||||
|
||||
@property (nonatomic, readonly) RCTDevMenu *devMenu;
|
||||
|
||||
@end
|
||||
|
@ -9,101 +9,220 @@
|
||||
|
||||
#import "RCTDevMenu.h"
|
||||
|
||||
#import "RCTRedBox.h"
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTProfile.h"
|
||||
#import "RCTRootView.h"
|
||||
#import "RCTSourceCode.h"
|
||||
#import "RCTWebViewExecutor.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
@interface RCTBridge (RCTDevMenu)
|
||||
|
||||
@property (nonatomic, copy, readonly) NSArray *profile;
|
||||
@interface RCTBridge (Profiling)
|
||||
|
||||
- (void)startProfiling;
|
||||
- (void)stopProfiling;
|
||||
|
||||
@end
|
||||
|
||||
static NSString *const RCTShowDevMenuNotification = @"RCTShowDevMenuNotification";
|
||||
|
||||
@implementation UIWindow (RCTDevMenu)
|
||||
|
||||
- (void)RCT_motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
|
||||
{
|
||||
if (event.subtype == UIEventSubtypeMotionShake) {
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTShowDevMenuNotification object:nil];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface RCTDevMenu () <UIActionSheetDelegate>
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTDevMenu
|
||||
{
|
||||
BOOL _liveReload;
|
||||
__weak RCTBridge *_bridge;
|
||||
NSTimer *_updateTimer;
|
||||
UIActionSheet *_actionSheet;
|
||||
}
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_bridge = bridge;
|
||||
// We're swizzling here because it's poor form to override methods in a category,
|
||||
// however UIWindow doesn't actually implement motionEnded:withEvent:, so there's
|
||||
// no need to call the original implementation.
|
||||
RCTSwapInstanceMethods([UIWindow class], @selector(motionEnded:withEvent:), @selector(RCT_motionEnded:withEvent:));
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
|
||||
_shakeToShow = YES;
|
||||
_liveReloadPeriod = 1.0; // 1 second
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(showOnShake)
|
||||
name:RCTShowDevMenuNotification
|
||||
object:nil];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
- (void)showOnShake
|
||||
{
|
||||
if (_shakeToShow) {
|
||||
[self show];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)show
|
||||
{
|
||||
NSString *debugTitleChrome = _bridge.executorClass != Nil && _bridge.executorClass == NSClassFromString(@"RCTWebSocketExecutor") ? @"Disable Chrome Debugging" : @"Enable Chrome Debugging";
|
||||
NSString *debugTitleSafari = _bridge.executorClass == [RCTWebViewExecutor class] ? @"Disable Safari Debugging" : @"Enable Safari Debugging";
|
||||
NSString *liveReloadTitle = _liveReload ? @"Disable Live Reload" : @"Enable Live Reload";
|
||||
NSString *profilingTitle = _bridge.profile ? @"Stop Profiling" : @"Start Profiling";
|
||||
UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"React Native: Development"
|
||||
delegate:self
|
||||
cancelButtonTitle:@"Cancel"
|
||||
destructiveButtonTitle:nil
|
||||
otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, liveReloadTitle, profilingTitle, nil];
|
||||
if (_actionSheet) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *debugTitleChrome = _bridge.executorClass && _bridge.executorClass == NSClassFromString(@"RCTWebSocketExecutor") ? @"Disable Chrome Debugging" : @"Enable Chrome Debugging";
|
||||
NSString *debugTitleSafari = _bridge.executorClass && _bridge.executorClass == NSClassFromString(@"RCTWebViewExecutor") ? @"Disable Safari Debugging" : @"Enable Safari Debugging";
|
||||
NSString *liveReloadTitle = _liveReloadEnabled ? @"Disable Live Reload" : @"Enable Live Reload";
|
||||
NSString *profilingTitle = RCTProfileIsProfiling() ? @"Stop Profiling" : @"Start Profiling";
|
||||
|
||||
UIActionSheet *actionSheet =
|
||||
[[UIActionSheet alloc] initWithTitle:@"React Native: Development"
|
||||
delegate:self
|
||||
cancelButtonTitle:@"Cancel"
|
||||
destructiveButtonTitle:nil
|
||||
otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, liveReloadTitle, profilingTitle, nil];
|
||||
|
||||
actionSheet.actionSheetStyle = UIBarStyleBlack;
|
||||
[actionSheet showInView:[[[[UIApplication sharedApplication] keyWindow] rootViewController] view]];
|
||||
[actionSheet showInView:[UIApplication sharedApplication].keyWindow.rootViewController.view];
|
||||
}
|
||||
|
||||
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
|
||||
{
|
||||
if (buttonIndex == 0) {
|
||||
[_bridge reload];
|
||||
} else if (buttonIndex == 1) {
|
||||
Class cls = NSClassFromString(@"RCTWebSocketExecutor");
|
||||
_bridge.executorClass = (_bridge.executorClass != cls) ? cls : nil;
|
||||
[_bridge reload];
|
||||
} else if (buttonIndex == 2) {
|
||||
Class cls = [RCTWebViewExecutor class];
|
||||
_bridge.executorClass = (_bridge.executorClass != cls) ? cls : Nil;
|
||||
[_bridge reload];
|
||||
} else if (buttonIndex == 3) {
|
||||
_liveReload = !_liveReload;
|
||||
[self _pollAndReload];
|
||||
} else if (buttonIndex == 4) {
|
||||
if (_bridge.profile) {
|
||||
[_bridge stopProfiling];
|
||||
} else {
|
||||
[_bridge startProfiling];
|
||||
}
|
||||
}
|
||||
}
|
||||
_actionSheet = nil;
|
||||
|
||||
- (void)_pollAndReload
|
||||
{
|
||||
if (_liveReload) {
|
||||
RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
|
||||
NSURL *url = sourceCodeModule.scriptURL;
|
||||
NSURL *longPollURL = [[NSURL alloc] initWithString:@"/onchange" relativeToURL:url];
|
||||
[self performSelectorInBackground:@selector(_checkForUpdates:) withObject:longPollURL];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_checkForUpdates:(NSURL *)URL
|
||||
{
|
||||
NSMutableURLRequest *longPollRequest = [NSMutableURLRequest requestWithURL:URL];
|
||||
longPollRequest.timeoutInterval = 30;
|
||||
NSHTTPURLResponse *response;
|
||||
[NSURLConnection sendSynchronousRequest:longPollRequest returningResponse:&response error:nil];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (_liveReload && response.statusCode == 205) {
|
||||
[[RCTRedBox sharedInstance] dismiss];
|
||||
switch (buttonIndex) {
|
||||
case 0: {
|
||||
[_bridge reload];
|
||||
break;
|
||||
}
|
||||
[self _pollAndReload];
|
||||
});
|
||||
case 1: {
|
||||
Class cls = NSClassFromString(@"RCTWebSocketExecutor");
|
||||
_bridge.executorClass = (_bridge.executorClass != cls) ? cls : nil;
|
||||
[_bridge reload];
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
Class cls = NSClassFromString(@"RCTWebViewExecutor");
|
||||
_bridge.executorClass = (_bridge.executorClass != cls) ? cls : Nil;
|
||||
[_bridge reload];
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
self.liveReloadEnabled = !_liveReloadEnabled;
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
self.profilingEnabled = !_profilingEnabled;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setProfilingEnabled:(BOOL)enabled
|
||||
{
|
||||
if (_profilingEnabled == enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
_profilingEnabled = enabled;
|
||||
if (RCTProfileIsProfiling()) {
|
||||
[_bridge stopProfiling];
|
||||
} else {
|
||||
[_bridge startProfiling];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setLiveReloadEnabled:(BOOL)enabled
|
||||
{
|
||||
if (_liveReloadEnabled == enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
_liveReloadEnabled = enabled;
|
||||
if (_liveReloadEnabled) {
|
||||
|
||||
_updateTimer = [NSTimer scheduledTimerWithTimeInterval:_liveReloadPeriod
|
||||
target:self
|
||||
selector:@selector(pollForUpdates)
|
||||
userInfo:nil
|
||||
repeats:YES];
|
||||
} else {
|
||||
|
||||
[_updateTimer invalidate];
|
||||
_updateTimer = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setLiveReloadPeriod:(NSTimeInterval)liveReloadPeriod
|
||||
{
|
||||
_liveReloadPeriod = liveReloadPeriod;
|
||||
if (_liveReloadEnabled) {
|
||||
self.liveReloadEnabled = NO;
|
||||
self.liveReloadEnabled = YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)pollForUpdates
|
||||
{
|
||||
RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
|
||||
if (!sourceCodeModule) {
|
||||
RCTLogError(@"RCTSourceCode module not found");
|
||||
self.liveReloadEnabled = NO;
|
||||
}
|
||||
|
||||
NSURL *longPollURL = [[NSURL alloc] initWithString:@"/onchange" relativeToURL:sourceCodeModule.scriptURL];
|
||||
[NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:longPollURL]
|
||||
queue:[[NSOperationQueue alloc] init]
|
||||
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
|
||||
|
||||
NSHTTPURLResponse *HTTPResponse = (NSHTTPURLResponse *)response;
|
||||
if (_liveReloadEnabled && HTTPResponse.statusCode == 205) {
|
||||
[_bridge reload];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (BOOL)isValid
|
||||
{
|
||||
return !_liveReloadEnabled || _updateTimer != nil;
|
||||
}
|
||||
|
||||
- (void)invalidate
|
||||
{
|
||||
[_actionSheet dismissWithClickedButtonIndex:_actionSheet.cancelButtonIndex animated:YES];
|
||||
[_updateTimer invalidate];
|
||||
_updateTimer = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTBridge (RCTDevMenu)
|
||||
|
||||
- (RCTDevMenu *)devMenu
|
||||
{
|
||||
return self.modules[RCTBridgeModuleNameForClass([RCTDevMenu class])];
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -7,6 +7,8 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <objc/runtime.h>
|
||||
|
||||
#import <JavaScriptCore/JavaScriptCore.h>
|
||||
|
||||
#import "RCTInvalidating.h"
|
||||
@ -27,6 +29,7 @@ typedef void (^RCTJavaScriptCallback)(id json, NSError *error);
|
||||
- (void)executeJSCall:(NSString *)name
|
||||
method:(NSString *)method
|
||||
arguments:(NSArray *)arguments
|
||||
context:(NSNumber *)executorID
|
||||
callback:(RCTJavaScriptCallback)onComplete;
|
||||
|
||||
/**
|
||||
@ -39,4 +42,27 @@ typedef void (^RCTJavaScriptCallback)(id json, NSError *error);
|
||||
- (void)injectJSONText:(NSString *)script
|
||||
asGlobalObjectNamed:(NSString *)objectName
|
||||
callback:(RCTJavaScriptCompleteBlock)onComplete;
|
||||
|
||||
/**
|
||||
* Enqueue a block to run in the executors JS thread. Fallback to `dispatch_async`
|
||||
* on the main queue if the executor doesn't own a thread.
|
||||
*/
|
||||
- (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block;
|
||||
|
||||
@end
|
||||
|
||||
static const char *RCTJavaScriptExecutorID = "RCTJavaScriptExecutorID";
|
||||
__used static id<RCTJavaScriptExecutor> RCTCreateExecutor(Class executorClass)
|
||||
{
|
||||
static NSUInteger executorID = 0;
|
||||
id<RCTJavaScriptExecutor> executor = [[executorClass alloc] init];
|
||||
if (executor) {
|
||||
objc_setAssociatedObject(executor, RCTJavaScriptExecutorID, @(++executorID), OBJC_ASSOCIATION_RETAIN);
|
||||
}
|
||||
return executor;
|
||||
}
|
||||
|
||||
__used static NSNumber *RCTGetExecutorID(id<RCTJavaScriptExecutor> executor)
|
||||
{
|
||||
return executor ? objc_getAssociatedObject(executor, RCTJavaScriptExecutorID) : @0;
|
||||
}
|
||||
|
@ -1,4 +1,11 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
|
@ -1,4 +1,11 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import "RCTJavaScriptLoader.h"
|
||||
|
||||
@ -78,8 +85,9 @@
|
||||
// Handle general request errors
|
||||
if (error) {
|
||||
if ([[error domain] isEqualToString:NSURLErrorDomain]) {
|
||||
NSString *desc = [@"Could not connect to development server. Ensure node server is running and available on the same network - run 'npm start' from react-native root\n\nURL: " stringByAppendingString:[scriptURL absoluteString]];
|
||||
NSDictionary *userInfo = @{
|
||||
NSLocalizedDescriptionKey: @"Could not connect to development server. Ensure node server is running - run 'npm start' from ReactKit root",
|
||||
NSLocalizedDescriptionKey: desc,
|
||||
NSLocalizedFailureReasonErrorKey: [error localizedDescription],
|
||||
NSUnderlyingErrorKey: error,
|
||||
};
|
||||
@ -133,9 +141,9 @@
|
||||
sourceCodeModule.scriptURL = scriptURL;
|
||||
sourceCodeModule.scriptText = rawText;
|
||||
|
||||
[_bridge enqueueApplicationScript:rawText url:scriptURL onComplete:^(NSError *_error) {
|
||||
[_bridge enqueueApplicationScript:rawText url:scriptURL onComplete:^(NSError *scriptError) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
onComplete(_error);
|
||||
onComplete(scriptError);
|
||||
});
|
||||
}];
|
||||
}];
|
||||
|
@ -10,10 +10,7 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "RCTAssert.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#import "RCTDefines.h"
|
||||
|
||||
/**
|
||||
* Thresholds for logs to raise an assertion, or display redbox, respectively.
|
||||
@ -45,11 +42,16 @@ typedef void (^RCTLogFunction)(
|
||||
NSString *message
|
||||
);
|
||||
|
||||
/**
|
||||
* Get a given thread's name (or the current queue, if in debug mode)
|
||||
*/
|
||||
RCT_EXTERN NSString *RCTThreadName(NSThread *);
|
||||
|
||||
/**
|
||||
* A method to generate a string from a collection of log data. To omit any
|
||||
* particular data from the log, just pass nil or zero for the argument.
|
||||
*/
|
||||
NSString *RCTFormatLog(
|
||||
RCT_EXTERN NSString *RCTFormatLog(
|
||||
NSDate *timestamp,
|
||||
NSThread *thread,
|
||||
RCTLogLevel level,
|
||||
@ -68,35 +70,35 @@ extern RCTLogFunction RCTDefaultLogFunction;
|
||||
* below which logs will be ignored. Default is RCTLogLevelInfo for debug and
|
||||
* RCTLogLevelError for production.
|
||||
*/
|
||||
void RCTSetLogThreshold(RCTLogLevel threshold);
|
||||
RCTLogLevel RCTGetLogThreshold(void);
|
||||
RCT_EXTERN void RCTSetLogThreshold(RCTLogLevel threshold);
|
||||
RCT_EXTERN RCTLogLevel RCTGetLogThreshold(void);
|
||||
|
||||
/**
|
||||
* These methods get and set the current logging function called by the RCTLogXX
|
||||
* macros. You can use these to replace the standard behavior with custom log
|
||||
* functionality.
|
||||
*/
|
||||
void RCTSetLogFunction(RCTLogFunction logFunction);
|
||||
RCTLogFunction RCTGetLogFunction(void);
|
||||
RCT_EXTERN void RCTSetLogFunction(RCTLogFunction logFunction);
|
||||
RCT_EXTERN RCTLogFunction RCTGetLogFunction(void);
|
||||
|
||||
/**
|
||||
* This appends additional code to the existing log function, without replacing
|
||||
* the existing functionality. Useful if you just want to forward logs to an
|
||||
* extra service without changing the default behavior.
|
||||
*/
|
||||
void RCTAddLogFunction(RCTLogFunction logFunction);
|
||||
RCT_EXTERN void RCTAddLogFunction(RCTLogFunction logFunction);
|
||||
|
||||
/**
|
||||
* This method adds a conditional prefix to any messages logged within the scope
|
||||
* of the passed block. This is useful for adding additional context to log
|
||||
* messages. The block will be performed synchronously on the current thread.
|
||||
*/
|
||||
void RCTPerformBlockWithLogPrefix(void (^block)(void), NSString *prefix);
|
||||
RCT_EXTERN void RCTPerformBlockWithLogPrefix(void (^block)(void), NSString *prefix);
|
||||
|
||||
/**
|
||||
* Private logging functions - ignore these.
|
||||
*/
|
||||
void _RCTLogFormat(RCTLogLevel, const char *, int, NSString *, ...) NS_FORMAT_FUNCTION(4,5);
|
||||
RCT_EXTERN void _RCTLogFormat(RCTLogLevel, const char *, int, NSString *, ...) NS_FORMAT_FUNCTION(4,5);
|
||||
#define _RCTLog(lvl, ...) do { \
|
||||
if (lvl >= RCTLOG_FATAL_LEVEL) { RCTAssert(NO, __VA_ARGS__); } \
|
||||
_RCTLogFormat(lvl, __FILE__, __LINE__, __VA_ARGS__); \
|
||||
@ -111,7 +113,3 @@ void _RCTLogFormat(RCTLogLevel, const char *, int, NSString *, ...) NS_FORMAT_FU
|
||||
#define RCTLogWarn(...) _RCTLog(RCTLogLevelWarning, __VA_ARGS__)
|
||||
#define RCTLogError(...) _RCTLog(RCTLogLevelError, __VA_ARGS__)
|
||||
#define RCTLogMustFix(...) _RCTLog(RCTLogLevelMustFix, __VA_ARGS__)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
#import "RCTAssert.h"
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTDefines.h"
|
||||
#import "RCTRedBox.h"
|
||||
|
||||
@interface RCTBridge (Logging)
|
||||
@ -31,12 +32,12 @@ const char *RCTLogLevels[] = {
|
||||
static RCTLogFunction RCTCurrentLogFunction;
|
||||
static RCTLogLevel RCTCurrentLogThreshold;
|
||||
|
||||
void RCTLogSetup(void) __attribute__((constructor));
|
||||
void RCTLogSetup()
|
||||
__attribute__((constructor))
|
||||
static void RCTLogSetup()
|
||||
{
|
||||
RCTCurrentLogFunction = RCTDefaultLogFunction;
|
||||
|
||||
#if DEBUG
|
||||
#if RCT_DEBUG
|
||||
RCTCurrentLogThreshold = RCTLogLevelInfo - 1;
|
||||
#else
|
||||
RCTCurrentLogThreshold = RCTLogLevelError;
|
||||
@ -98,6 +99,22 @@ void RCTPerformBlockWithLogPrefix(void (^block)(void), NSString *prefix)
|
||||
[prefixStack removeLastObject];
|
||||
}
|
||||
|
||||
NSString *RCTThreadName(NSThread *thread)
|
||||
{
|
||||
NSString *threadName = [thread isMainThread] ? @"main" : thread.name;
|
||||
if (threadName.length == 0) {
|
||||
#if DEBUG // This is DEBUG not RCT_DEBUG because it *really* must not ship in RC
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
threadName = @(dispatch_queue_get_label(dispatch_get_current_queue()));
|
||||
#pragma clang diagnostic pop
|
||||
#else
|
||||
threadName = [NSString stringWithFormat:@"%p", thread];
|
||||
#endif
|
||||
}
|
||||
return threadName;
|
||||
}
|
||||
|
||||
NSString *RCTFormatLog(
|
||||
NSDate *timestamp,
|
||||
NSThread *thread,
|
||||
@ -121,18 +138,7 @@ NSString *RCTFormatLog(
|
||||
[log appendFormat:@"[%s]", RCTLogLevels[level - 1]];
|
||||
}
|
||||
if (thread) {
|
||||
NSString *threadName = [thread isMainThread] ? @"main" : thread.name;
|
||||
if (threadName.length == 0) {
|
||||
#if DEBUG
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
threadName = @(dispatch_queue_get_label(dispatch_get_current_queue()));
|
||||
#pragma clang diagnostic pop
|
||||
#else
|
||||
threadName = [NSString stringWithFormat:@"%p", thread];
|
||||
#endif
|
||||
}
|
||||
[log appendFormat:@"[tid:%@]", threadName];
|
||||
[log appendFormat:@"[tid:%@]", RCTThreadName(thread)];
|
||||
}
|
||||
if (fileName) {
|
||||
fileName = [fileName lastPathComponent];
|
||||
@ -156,12 +162,7 @@ void _RCTLogFormat(
|
||||
NSString *format, ...)
|
||||
{
|
||||
|
||||
#if DEBUG
|
||||
BOOL log = YES;
|
||||
#else
|
||||
BOOL log = (RCTCurrentLogFunction != nil);
|
||||
#endif
|
||||
|
||||
BOOL log = RCT_DEBUG || (RCTCurrentLogFunction != nil);
|
||||
if (log && level >= RCTCurrentLogThreshold) {
|
||||
|
||||
// Get message
|
||||
@ -183,15 +184,15 @@ void _RCTLogFormat(
|
||||
level, fileName ? @(fileName) : nil, (lineNumber >= 0) ? @(lineNumber) : nil, message
|
||||
);
|
||||
|
||||
#if DEBUG
|
||||
#if RCT_DEBUG // Red box is only available in debug mode
|
||||
|
||||
// Log to red box
|
||||
if (level >= RCTLOG_REDBOX_LEVEL) {
|
||||
[[RCTRedBox sharedInstance] showErrorMessage:message];
|
||||
}
|
||||
// Log to red box
|
||||
if (level >= RCTLOG_REDBOX_LEVEL) {
|
||||
[[RCTRedBox sharedInstance] showErrorMessage:message];
|
||||
}
|
||||
|
||||
// Log to JS executor
|
||||
[RCTBridge logMessage:message level:level ? @(RCTLogLevels[level - 1]) : @"info"];
|
||||
// Log to JS executor
|
||||
[RCTBridge logMessage:message level:level ? @(RCTLogLevels[level - 1]) : @"info"];
|
||||
|
||||
#endif
|
||||
|
||||
|
105
React/Base/RCTProfile.h
Normal file
105
React/Base/RCTProfile.h
Normal file
@ -0,0 +1,105 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "RCTDefines.h"
|
||||
|
||||
/**
|
||||
* RCTProfile
|
||||
*
|
||||
* This file provides a set of functions and macros for performance profiling
|
||||
*
|
||||
* NOTE: This API is a work in a work in progress, please consider carefully
|
||||
* before before using it.
|
||||
*/
|
||||
|
||||
#if RCT_DEV
|
||||
|
||||
/**
|
||||
* Returns YES if the profiling information is currently being collected
|
||||
*/
|
||||
RCT_EXTERN BOOL RCTProfileIsProfiling(void);
|
||||
|
||||
/**
|
||||
* Start collecting profiling information
|
||||
*/
|
||||
RCT_EXTERN void RCTProfileInit(void);
|
||||
|
||||
/**
|
||||
* Stop profiling and return a JSON string of the collected data - The data
|
||||
* returned is compliant with google's trace event format - the format used
|
||||
* as input to trace-viewer
|
||||
*/
|
||||
RCT_EXTERN NSString *RCTProfileEnd(void);
|
||||
|
||||
/**
|
||||
* Collects the initial event information for the event and returns a reference ID
|
||||
*/
|
||||
RCT_EXTERN NSNumber *_RCTProfileBeginEvent(void);
|
||||
|
||||
/**
|
||||
* The ID returned by BeginEvent should then be passed into EndEvent, with the
|
||||
* rest of the event information. Just at this point the event will actually be
|
||||
* registered
|
||||
*/
|
||||
RCT_EXTERN void _RCTProfileEndEvent(NSNumber *, NSString *, NSString *, id);
|
||||
|
||||
/**
|
||||
* This pair of macros implicitly handle the event ID when beginning and ending
|
||||
* an event, for both simplicity and performance reasons, this method is preferred
|
||||
*
|
||||
* NOTE: The EndEvent call has to be either, in the same scope of BeginEvent,
|
||||
* or in a sub-scope, otherwise the ID stored by BeginEvent won't be accessible
|
||||
* for EndEvent, in this case you may want to use the actual C functions.
|
||||
*/
|
||||
#define RCTProfileBeginEvent() \
|
||||
_Pragma("clang diagnostic push") \
|
||||
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
|
||||
NSNumber *__rct_profile_id = _RCTProfileBeginEvent(); \
|
||||
_Pragma("clang diagnostic pop")
|
||||
|
||||
#define RCTProfileEndEvent(name, category, args...) \
|
||||
_RCTProfileEndEvent(__rct_profile_id, name, category, args)
|
||||
|
||||
/**
|
||||
* An event that doesn't have a duration (i.e. Notification, VSync, etc)
|
||||
*/
|
||||
RCT_EXTERN void RCTProfileImmediateEvent(NSString *, NSTimeInterval , NSString *);
|
||||
|
||||
/**
|
||||
* Helper to profile the duration of the execution of a block. This method uses
|
||||
* self and _cmd to name this event for simplicity sake.
|
||||
*
|
||||
* NOTE: The block can't expect any argument
|
||||
*/
|
||||
#define RCTProfileBlock(block, category, arguments) \
|
||||
^{ \
|
||||
RCTProfileBeginEvent(); \
|
||||
block(); \
|
||||
RCTProfileEndEvent([NSString stringWithFormat:@"[%@ %@]", NSStringFromClass([self class]), NSStringFromSelector(_cmd)], category, arguments); \
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#define RCTProfileIsProfiling(...) NO
|
||||
#define RCTProfileInit(...)
|
||||
#define RCTProfileEnd(...) @""
|
||||
|
||||
#define _RCTProfileBeginEvent(...) @0
|
||||
#define RCTProfileBeginEvent(...)
|
||||
|
||||
#define _RCTProfileEndEvent(...)
|
||||
#define RCTProfileEndEvent(...)
|
||||
|
||||
#define RCTProfileImmediateEvent(...)
|
||||
|
||||
#define RCTProfileBlock(block, ...) block
|
||||
|
||||
#endif
|
174
React/Base/RCTProfile.m
Normal file
174
React/Base/RCTProfile.m
Normal file
@ -0,0 +1,174 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import "RCTProfile.h"
|
||||
|
||||
#import <mach/mach.h>
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTDefines.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
#if RCT_DEV
|
||||
|
||||
#pragma mark - Prototypes
|
||||
|
||||
NSNumber *RCTProfileTimestamp(NSTimeInterval);
|
||||
NSString *RCTProfileMemory(vm_size_t);
|
||||
NSDictionary *RCTProfileGetMemoryUsage(void);
|
||||
|
||||
#pragma mark - Constants
|
||||
|
||||
NSString const *RCTProfileTraceEvents = @"traceEvents";
|
||||
NSString const *RCTProfileSamples = @"samples";
|
||||
|
||||
#pragma mark - Variables
|
||||
|
||||
NSDictionary *RCTProfileInfo;
|
||||
NSUInteger RCTProfileEventID = 0;
|
||||
NSMutableDictionary *RCTProfileOngoingEvents;
|
||||
NSTimeInterval RCTProfileStartTime;
|
||||
NSLock *_RCTProfileLock;
|
||||
|
||||
#pragma mark - Macros
|
||||
|
||||
#define RCTProfileAddEvent(type, props...) \
|
||||
[RCTProfileInfo[type] addObject:@{ \
|
||||
@"pid": @([[NSProcessInfo processInfo] processIdentifier]), \
|
||||
@"tid": RCTThreadName([NSThread currentThread]), \
|
||||
props \
|
||||
}];
|
||||
|
||||
#define CHECK(...) \
|
||||
if (!RCTProfileIsProfiling()) { \
|
||||
return __VA_ARGS__; \
|
||||
}
|
||||
|
||||
#define RCTProfileLock(...) \
|
||||
[_RCTProfileLock lock]; \
|
||||
__VA_ARGS__ \
|
||||
[_RCTProfileLock unlock]
|
||||
|
||||
#pragma mark - Private Helpers
|
||||
|
||||
NSNumber *RCTProfileTimestamp(NSTimeInterval timestamp)
|
||||
{
|
||||
return @((timestamp - RCTProfileStartTime) * 1e6);
|
||||
}
|
||||
|
||||
NSString *RCTProfileMemory(vm_size_t memory)
|
||||
{
|
||||
double mem = ((double)memory) / 1024 / 1024;
|
||||
return [NSString stringWithFormat:@"%.2lfmb", mem];
|
||||
}
|
||||
|
||||
NSDictionary *RCTProfileGetMemoryUsage(void)
|
||||
{
|
||||
struct task_basic_info info;
|
||||
mach_msg_type_number_t size = sizeof(info);
|
||||
kern_return_t kerr = task_info(mach_task_self(),
|
||||
TASK_BASIC_INFO,
|
||||
(task_info_t)&info,
|
||||
&size);
|
||||
if( kerr == KERN_SUCCESS ) {
|
||||
return @{
|
||||
@"suspend_count": @(info.suspend_count),
|
||||
@"virtual_size": RCTProfileMemory(info.virtual_size),
|
||||
@"resident_size": RCTProfileMemory(info.resident_size),
|
||||
};
|
||||
} else {
|
||||
return @{};
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Public Functions
|
||||
|
||||
BOOL RCTProfileIsProfiling(void)
|
||||
{
|
||||
RCTProfileLock(
|
||||
BOOL profiling = RCTProfileInfo != nil;
|
||||
);
|
||||
return profiling;
|
||||
}
|
||||
|
||||
void RCTProfileInit(void)
|
||||
{
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
_RCTProfileLock = [[NSLock alloc] init];
|
||||
});
|
||||
RCTProfileLock(
|
||||
RCTProfileStartTime = CACurrentMediaTime();
|
||||
RCTProfileOngoingEvents = [[NSMutableDictionary alloc] init];
|
||||
RCTProfileInfo = @{
|
||||
RCTProfileTraceEvents: [[NSMutableArray alloc] init],
|
||||
RCTProfileSamples: [[NSMutableArray alloc] init],
|
||||
};
|
||||
);
|
||||
}
|
||||
|
||||
NSString *RCTProfileEnd(void)
|
||||
{
|
||||
RCTProfileLock(
|
||||
NSString *log = RCTJSONStringify(RCTProfileInfo, NULL);
|
||||
RCTProfileEventID = 0;
|
||||
RCTProfileInfo = nil;
|
||||
RCTProfileOngoingEvents = nil;
|
||||
);
|
||||
return log;
|
||||
}
|
||||
|
||||
NSNumber *_RCTProfileBeginEvent(void)
|
||||
{
|
||||
CHECK(@0);
|
||||
RCTProfileLock(
|
||||
NSNumber *eventID = @(++RCTProfileEventID);
|
||||
RCTProfileOngoingEvents[eventID] = RCTProfileTimestamp(CACurrentMediaTime());
|
||||
);
|
||||
return eventID;
|
||||
}
|
||||
|
||||
void _RCTProfileEndEvent(NSNumber *eventID, NSString *name, NSString *categories, id args)
|
||||
{
|
||||
CHECK();
|
||||
RCTProfileLock(
|
||||
NSNumber *startTimestamp = RCTProfileOngoingEvents[eventID];
|
||||
if (startTimestamp) {
|
||||
NSNumber *endTimestamp = RCTProfileTimestamp(CACurrentMediaTime());
|
||||
|
||||
RCTProfileAddEvent(RCTProfileTraceEvents,
|
||||
@"name": name,
|
||||
@"cat": categories,
|
||||
@"ph": @"X",
|
||||
@"ts": startTimestamp,
|
||||
@"dur": @(endTimestamp.doubleValue - startTimestamp.doubleValue),
|
||||
@"args": args ?: @[],
|
||||
);
|
||||
[RCTProfileOngoingEvents removeObjectForKey:eventID];
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void RCTProfileImmediateEvent(NSString *name, NSTimeInterval timestamp, NSString *scope)
|
||||
{
|
||||
CHECK();
|
||||
RCTProfileLock(
|
||||
RCTProfileAddEvent(RCTProfileTraceEvents,
|
||||
@"name": name,
|
||||
@"ts": RCTProfileTimestamp(timestamp),
|
||||
@"scope": scope,
|
||||
@"ph": @"i",
|
||||
@"args": RCTProfileGetMemoryUsage(),
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
#endif
|
@ -7,6 +7,10 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTDefines.h"
|
||||
|
||||
#if RCT_DEBUG // Red box is only available in debug mode
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface RCTRedBox : NSObject
|
||||
@ -23,3 +27,5 @@
|
||||
- (void)dismiss;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
|
@ -7,6 +7,10 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTDefines.h"
|
||||
|
||||
#if RCT_DEBUG // Red box is only available in debug mode
|
||||
|
||||
#import "RCTRedBox.h"
|
||||
|
||||
#import "RCTBridge.h"
|
||||
@ -282,23 +286,12 @@
|
||||
|
||||
- (void)showErrorMessage:(NSString *)message withStack:(NSArray *)stack showIfHidden:(BOOL)shouldShow
|
||||
{
|
||||
|
||||
#if DEBUG
|
||||
|
||||
dispatch_block_t block = ^{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (!_window) {
|
||||
_window = [[RCTRedBoxWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
||||
}
|
||||
[_window showErrorMessage:message withStack:stack showIfHidden:shouldShow];
|
||||
};
|
||||
if ([NSThread isMainThread]) {
|
||||
block();
|
||||
} else {
|
||||
dispatch_async(dispatch_get_main_queue(), block);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
- (NSString *)currentErrorMessage
|
||||
@ -316,3 +309,5 @@
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
|
@ -57,12 +57,6 @@
|
||||
*/
|
||||
@property (nonatomic, strong) Class executorClass;
|
||||
|
||||
/**
|
||||
* If YES will watch for shake gestures and show development menu
|
||||
* with options like "Reload", "Enable Debugging", etc.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL enableDevMenu;
|
||||
|
||||
/**
|
||||
* The backing view controller of the root view.
|
||||
*/
|
||||
|
@ -13,7 +13,6 @@
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTContextExecutor.h"
|
||||
#import "RCTDevMenu.h"
|
||||
#import "RCTEventDispatcher.h"
|
||||
#import "RCTKeyCommands.h"
|
||||
#import "RCTLog.h"
|
||||
@ -42,7 +41,6 @@
|
||||
|
||||
@implementation RCTRootView
|
||||
{
|
||||
RCTDevMenu *_devMenu;
|
||||
RCTBridge *_bridge;
|
||||
RCTTouchHandler *_touchHandler;
|
||||
NSString *_moduleName;
|
||||
@ -60,12 +58,6 @@
|
||||
|
||||
self.backgroundColor = [UIColor whiteColor];
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
_enableDevMenu = YES;
|
||||
|
||||
#endif
|
||||
|
||||
_bridge = bridge;
|
||||
_moduleName = moduleName;
|
||||
|
||||
@ -120,18 +112,6 @@
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
|
||||
{
|
||||
if (motion == UIEventSubtypeMotionShake && self.enableDevMenu) {
|
||||
if (!_devMenu) {
|
||||
_devMenu = [[RCTDevMenu alloc] initWithBridge:_bridge];
|
||||
}
|
||||
[_devMenu show];
|
||||
} else {
|
||||
[super motionEnded:motion withEvent:event];
|
||||
}
|
||||
}
|
||||
|
||||
RCT_IMPORT_METHOD(AppRegistry, runApplication)
|
||||
RCT_IMPORT_METHOD(ReactIOS, unmountComponentAtNodeAndRemoveContainer)
|
||||
|
||||
@ -169,7 +149,7 @@ RCT_IMPORT_METHOD(ReactIOS, unmountComponentAtNodeAndRemoveContainer)
|
||||
[super layoutSubviews];
|
||||
if (_contentView) {
|
||||
_contentView.frame = self.bounds;
|
||||
[_bridge.uiManager setFrame:self.frame forRootView:_contentView];
|
||||
[_bridge.uiManager setFrame:self.bounds forRootView:_contentView];
|
||||
}
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user