Updates from Fri 3 Apr
- Update deps order - core modules first | Amjad Masad - [ReactNative] Workaround double cmd+r bug | Christopher Chedeau - [react-native] Nicer error for undefined or string tag names | Ben Alpert - [ReactNative] Fix script load from local files | Tadeu Zagallo - [react_native] JS files from D1961099: Format stack trace on native side | Alex Kotliarskyi - [ReactNative] Cleanup TabBar and its example | Christopher Chedeau - [ReactNative] Allow recover from debugger error | Tadeu Zagallo - [react-native] Update react to 0.13.1, jstransform alongside | Ben Alpert - Fixed tap-to-zoom in Groups photo viewer | Sumeet Vaidya - Fix hitTest for auto | Tadeu Zagallo - [ReactNative] Unfork RKRootView | Tadeu Zagallo - [react-packager] Ignore dotfiles in file watching | Amjad Masad
This commit is contained in:
parent
d82dbdff2d
commit
c32d9bc423
|
@ -55,29 +55,25 @@ class JumpingNavBar extends React.Component {
|
|||
render() {
|
||||
return (
|
||||
<View style={styles.tabs}>
|
||||
<TabBarIOS
|
||||
selectedTab={'tab_' + this.props.tabIndex}>
|
||||
<TabBarIOS>
|
||||
<TabBarIOS.Item
|
||||
name="tab_0"
|
||||
icon={require('image!tabnav_notification')}
|
||||
selected={this.props.tabIndex === 0}
|
||||
onPress={() => { this.props.onTabIndex(0); }}
|
||||
children={<View />}
|
||||
/>
|
||||
onPress={() => { this.props.onTabIndex(0); }}>
|
||||
<View />
|
||||
</TabBarIOS.Item>
|
||||
<TabBarIOS.Item
|
||||
name="tab_1"
|
||||
icon={require('image!tabnav_list')}
|
||||
selected={this.props.tabIndex === 1}
|
||||
onPress={() => { this.props.onTabIndex(1); }}
|
||||
children={<View />}
|
||||
/>
|
||||
onPress={() => { this.props.onTabIndex(1); }}>
|
||||
<View />
|
||||
</TabBarIOS.Item>
|
||||
<TabBarIOS.Item
|
||||
name="tab_2"
|
||||
icon={require('image!tabnav_settings')}
|
||||
selected={this.props.tabIndex === 2}
|
||||
onPress={() => { this.props.onTabIndex(2); }}
|
||||
children={<View />}
|
||||
/>
|
||||
onPress={() => { this.props.onTabIndex(2); }}>
|
||||
<View />
|
||||
</TabBarIOS.Item>
|
||||
</TabBarIOS>
|
||||
</View>
|
||||
);
|
||||
|
|
|
@ -22,9 +22,8 @@ var {
|
|||
Text,
|
||||
View,
|
||||
} = React;
|
||||
var TabBarItemIOS = TabBarIOS.Item;
|
||||
var TabBarExample = React.createClass({
|
||||
|
||||
var TabBarExample = React.createClass({
|
||||
statics: {
|
||||
title: '<TabBarIOS>',
|
||||
description: 'Tab-based navigation.'
|
||||
|
@ -42,19 +41,16 @@ var TabBarExample = React.createClass({
|
|||
return (
|
||||
<View style={[styles.tabContent, {backgroundColor: color}]}>
|
||||
<Text style={styles.tabText}>{pageText}</Text>
|
||||
<Text style={styles.tabText}>{this.state.presses} re-renders of this tab</Text>
|
||||
<Text style={styles.tabText}>{this.state.presses} re-renders of the More tab</Text>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<TabBarIOS
|
||||
selectedTab={this.state.selectedTab}>
|
||||
<TabBarItemIOS
|
||||
name="blueTab"
|
||||
icon={_ix_DEPRECATED('favorites')}
|
||||
accessibilityLabel="Blue Tab"
|
||||
<TabBarIOS>
|
||||
<TabBarIOS.Item
|
||||
title="Blue Tab"
|
||||
selected={this.state.selectedTab === 'blueTab'}
|
||||
onPress={() => {
|
||||
this.setState({
|
||||
|
@ -62,12 +58,10 @@ var TabBarExample = React.createClass({
|
|||
});
|
||||
}}>
|
||||
{this._renderContent('#414A8C', 'Blue Tab')}
|
||||
</TabBarItemIOS>
|
||||
<TabBarItemIOS
|
||||
accessibilityLabel="Red Tab"
|
||||
name="redTab"
|
||||
icon={_ix_DEPRECATED('history')}
|
||||
badgeValue={this.state.notifCount ? String(this.state.notifCount) : null}
|
||||
</TabBarIOS.Item>
|
||||
<TabBarIOS.Item
|
||||
systemIcon="history"
|
||||
badge={this.state.notifCount > 0 ? this.state.notifCount : undefined}
|
||||
selected={this.state.selectedTab === 'redTab'}
|
||||
onPress={() => {
|
||||
this.setState({
|
||||
|
@ -76,11 +70,9 @@ var TabBarExample = React.createClass({
|
|||
});
|
||||
}}>
|
||||
{this._renderContent('#783E33', 'Red Tab')}
|
||||
</TabBarItemIOS>
|
||||
<TabBarItemIOS
|
||||
name="greenTab"
|
||||
icon={_ix_DEPRECATED('more')}
|
||||
accessibilityLabel="Green Tab"
|
||||
</TabBarIOS.Item>
|
||||
<TabBarIOS.Item
|
||||
systemIcon="more"
|
||||
selected={this.state.selectedTab === 'greenTab'}
|
||||
onPress={() => {
|
||||
this.setState({
|
||||
|
@ -89,7 +81,7 @@ var TabBarExample = React.createClass({
|
|||
});
|
||||
}}>
|
||||
{this._renderContent('#21551C', 'Green Tab')}
|
||||
</TabBarItemIOS>
|
||||
</TabBarIOS.Item>
|
||||
</TabBarIOS>
|
||||
);
|
||||
},
|
||||
|
@ -107,14 +99,4 @@ var styles = StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
// This is needed because the actual image may not exist as a file and
|
||||
// is used by the native code to load a system image.
|
||||
// TODO(nicklockwood): How can this fit our require system?
|
||||
function _ix_DEPRECATED(imageUri) {
|
||||
return {
|
||||
uri: imageUri,
|
||||
isStatic: true,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = TabBarExample;
|
||||
|
|
|
@ -357,7 +357,7 @@ var ScrollResponderMixin = {
|
|||
* A helper function to zoom to a specific rect in the scrollview.
|
||||
* @param {object} rect Should have shape {x, y, w, h}
|
||||
*/
|
||||
scrollResponderZoomTo: function(rect: { x: number; y: number; w: number; h: number; }) {
|
||||
scrollResponderZoomTo: function(rect: { x: number; y: number; width: number; height: number; }) {
|
||||
RCTUIManagerDeprecated.zoomToRect(this.getNodeHandle(), rect);
|
||||
},
|
||||
|
||||
|
|
|
@ -13,8 +13,9 @@
|
|||
|
||||
var React = require('React');
|
||||
var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
|
||||
var TabBarItemIOS = require('TabBarItemIOS');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
var TabBarItemIOS = require('TabBarItemIOS');
|
||||
var View = require('View');
|
||||
|
||||
var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass');
|
||||
|
||||
|
@ -22,6 +23,11 @@ var TabBarIOS = React.createClass({
|
|||
statics: {
|
||||
Item: TabBarItemIOS,
|
||||
},
|
||||
|
||||
propTypes: {
|
||||
style: View.propTypes.style,
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<RCTTabBar style={[styles.tabGroup, this.props.style]}>
|
||||
|
|
|
@ -24,12 +24,57 @@ var merge = require('merge');
|
|||
|
||||
var TabBarItemIOS = React.createClass({
|
||||
propTypes: {
|
||||
icon: Image.propTypes.source.isRequired,
|
||||
onPress: React.PropTypes.func.isRequired,
|
||||
selected: React.PropTypes.bool.isRequired,
|
||||
badgeValue: React.PropTypes.string,
|
||||
title: React.PropTypes.string,
|
||||
/**
|
||||
* Little red bubble that sits at the top right of the icon.
|
||||
*/
|
||||
badge: React.PropTypes.oneOfType([
|
||||
React.PropTypes.string,
|
||||
React.PropTypes.number,
|
||||
]),
|
||||
/**
|
||||
* Items comes with a few predefined system icons. Note that if you are
|
||||
* using them, the title and selectedIcon will be overriden with the
|
||||
* system ones.
|
||||
*/
|
||||
systemIcon: React.PropTypes.oneOf([
|
||||
'bookmarks',
|
||||
'contacts',
|
||||
'downloads',
|
||||
'favorites',
|
||||
'featured',
|
||||
'history',
|
||||
'more',
|
||||
'most-recent',
|
||||
'most-viewed',
|
||||
'recents',
|
||||
'search',
|
||||
'top-rated',
|
||||
]),
|
||||
/**
|
||||
* A custom icon for the tab. It is ignored when a system icon is defined.
|
||||
*/
|
||||
icon: Image.propTypes.source,
|
||||
/**
|
||||
* A custom icon when the tab is selected. It is ignored when a system
|
||||
* icon is defined. If left empty, the icon will be tinted in blue.
|
||||
*/
|
||||
selectedIcon: Image.propTypes.source,
|
||||
/**
|
||||
* Callback when this tab is being selected, you should change the state of your
|
||||
* component to set selected={true}.
|
||||
*/
|
||||
onPress: React.PropTypes.func,
|
||||
/**
|
||||
* It specifies whether the children are visible or not. If you see a
|
||||
* blank content, you probably forgot to add a selected one.
|
||||
*/
|
||||
selected: React.PropTypes.bool,
|
||||
style: View.propTypes.style,
|
||||
/**
|
||||
* Text that appears under the icon. It is ignored when a system icon
|
||||
* is defined.
|
||||
*/
|
||||
title: React.PropTypes.string,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
|
@ -63,13 +108,21 @@ var TabBarItemIOS = React.createClass({
|
|||
tabContents = <View />;
|
||||
}
|
||||
|
||||
var icon = this.props.systemIcon || (
|
||||
this.props.icon && this.props.icon.uri
|
||||
);
|
||||
|
||||
var badge = typeof this.props.badge === 'number' ?
|
||||
'' + this.props.badge :
|
||||
this.props.badge;
|
||||
|
||||
return (
|
||||
<RCTTabBarItem
|
||||
icon={this.props.icon.uri}
|
||||
icon={icon}
|
||||
selectedIcon={this.props.selectedIcon && this.props.selectedIcon.uri}
|
||||
onPress={this.props.onPress}
|
||||
selected={this.props.selected}
|
||||
badgeValue={this.props.badgeValue}
|
||||
badgeValue={badge}
|
||||
title={this.props.title}
|
||||
style={[styles.tab, this.props.style]}>
|
||||
{tabContents}
|
||||
|
|
|
@ -36,12 +36,12 @@ function handleException(e: Exception) {
|
|||
);
|
||||
|
||||
if (RCTExceptionsManager) {
|
||||
RCTExceptionsManager.reportUnhandledException(e.message, format(stack));
|
||||
RCTExceptionsManager.reportUnhandledException(e.message, stack);
|
||||
if (__DEV__) {
|
||||
(sourceMapPromise = sourceMapPromise || loadSourceMap())
|
||||
.then(map => {
|
||||
var prettyStack = parseErrorStack(e, map);
|
||||
RCTExceptionsManager.updateExceptionMessage(e.message, format(prettyStack));
|
||||
RCTExceptionsManager.updateExceptionMessage(e.message, prettyStack);
|
||||
})
|
||||
.then(null, error => {
|
||||
console.error('#CLOWNTOWN (error while displaying error): ' + error.message);
|
||||
|
@ -71,13 +71,4 @@ function fillSpaces(n) {
|
|||
return new Array(n + 1).join(' ');
|
||||
}
|
||||
|
||||
// HACK(frantic) Android currently expects stack trace to be a string #5920439
|
||||
function format(stack) {
|
||||
if (Platform.OS === 'android') {
|
||||
return stackToString(stack);
|
||||
} else {
|
||||
return stack;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { handleException };
|
||||
|
|
|
@ -21,6 +21,8 @@
|
|||
// This is used to give meaningful names to snapshot image files.
|
||||
@property (nonatomic, assign) SEL testSelector;
|
||||
|
||||
@property (nonatomic, weak) UIView *view;
|
||||
|
||||
- (instancetype)initWithSnapshotController:(FBSnapshotTestController *)controller view:(UIView *)view;
|
||||
|
||||
@end
|
||||
|
|
|
@ -17,15 +17,6 @@
|
|||
|
||||
#define TIMEOUT_SECONDS 240
|
||||
|
||||
@interface RCTRootView (Testing)
|
||||
|
||||
- (instancetype)_initWithBundleURL:(NSURL *)bundleURL
|
||||
moduleName:(NSString *)moduleName
|
||||
launchOptions:(NSDictionary *)launchOptions
|
||||
moduleProvider:(RCTBridgeModuleProviderBlock)moduleProvider;
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTTestRunner
|
||||
{
|
||||
FBSnapshotTestController *_snapshotController;
|
||||
|
@ -75,17 +66,17 @@
|
|||
|
||||
RCTTestModule *testModule = [[RCTTestModule alloc] initWithSnapshotController:_snapshotController view:nil];
|
||||
testModule.testSelector = test;
|
||||
|
||||
RCTRootView *rootView = [[RCTRootView alloc] _initWithBundleURL:[NSURL URLWithString:_script]
|
||||
moduleName:moduleName
|
||||
launchOptions:nil
|
||||
moduleProvider:^{
|
||||
return @[testModule];
|
||||
}];
|
||||
[testModule setValue:rootView forKey:@"_view"];
|
||||
rootView.frame = CGRectMake(0, 0, 320, 2000);
|
||||
RCTBridge *bridge = [[RCTBridge alloc] initWithBundlePath:_script
|
||||
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
|
||||
rootView.initialProperties = initialProps;
|
||||
rootView.frame = CGRectMake(0, 0, 320, 2000); // Constant size for testing on multiple devices
|
||||
|
||||
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
|
||||
NSString *error = [[RCTRedBox sharedInstance] currentErrorMessage];
|
||||
|
@ -94,8 +85,9 @@
|
|||
[[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:date];
|
||||
error = [[RCTRedBox sharedInstance] currentErrorMessage];
|
||||
}
|
||||
[rootView invalidate];
|
||||
[rootView removeFromSuperview];
|
||||
[rootView.bridge invalidate];
|
||||
[rootView invalidate];
|
||||
RCTAssert(vc.view.subviews.count == 0, @"There shouldn't be any other views: %@", vc.view);
|
||||
vc.view = nil;
|
||||
[[RCTRedBox sharedInstance] dismiss];
|
||||
|
|
|
@ -168,6 +168,7 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply);
|
|||
|
||||
- (void)invalidate
|
||||
{
|
||||
[_jsQueue cancelAllOperations];
|
||||
_socket.delegate = nil;
|
||||
[_socket closeWithCode:1000 reason:@"Invalidated"];
|
||||
_socket = nil;
|
||||
|
|
|
@ -38,6 +38,7 @@ var ResponderEventPlugin = require('ResponderEventPlugin');
|
|||
var UniversalWorkerNodeHandle = require('UniversalWorkerNodeHandle');
|
||||
|
||||
var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass');
|
||||
var invariant = require('invariant');
|
||||
|
||||
// Just to ensure this gets packaged, since its only caller is from Native.
|
||||
require('RCTEventEmitter');
|
||||
|
@ -94,6 +95,14 @@ function inject() {
|
|||
ReactNativeComponent.injection.injectTextComponentClass(
|
||||
ReactIOSTextComponent
|
||||
);
|
||||
ReactNativeComponent.injection.injectAutoWrapper(function(tag) {
|
||||
// Show a nicer error message for non-function tags
|
||||
var info = '';
|
||||
if (typeof tag === 'string' && /^[a-z]/.test(tag)) {
|
||||
info += ' Each component name should start with an uppercase letter.';
|
||||
}
|
||||
invariant(false, 'Expected a component class, got %s.%s', tag, info);
|
||||
});
|
||||
|
||||
NodeHandle.injection.injectImplementation(UniversalWorkerNodeHandle);
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTBridgeModule.h"
|
||||
#import "RCTInvalidating.h"
|
||||
#import "RCTJavaScriptExecutor.h"
|
||||
|
@ -24,11 +26,15 @@
|
|||
*/
|
||||
typedef NSArray *(^RCTBridgeModuleProviderBlock)(void);
|
||||
|
||||
extern NSString *const RCTReloadBridge;
|
||||
|
||||
/**
|
||||
* Async batched bridge used to communicate with the JavaScript application.
|
||||
*/
|
||||
@interface RCTBridge : NSObject <RCTInvalidating>
|
||||
|
||||
@property (nonatomic, assign, readonly, getter=isLoaded) BOOL loaded;
|
||||
|
||||
/**
|
||||
* The designated initializer. This creates a new bridge on top of the specified
|
||||
* executor. The bridge should then be used for all subsequent communication
|
||||
|
@ -55,6 +61,8 @@ typedef NSArray *(^RCTBridgeModuleProviderBlock)(void);
|
|||
*/
|
||||
- (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete;
|
||||
|
||||
@property (nonatomic, strong) Class executorClass;
|
||||
|
||||
/**
|
||||
* The event dispatcher is a wrapper around -enqueueJSCall:args: that provides a
|
||||
* higher-level interface for sending UI events such as touches and text input.
|
||||
|
@ -89,4 +97,6 @@ typedef NSArray *(^RCTBridgeModuleProviderBlock)(void);
|
|||
*/
|
||||
+ (BOOL)hasValidJSExecutor;
|
||||
|
||||
- (void)reload;
|
||||
|
||||
@end
|
||||
|
|
|
@ -16,11 +16,16 @@
|
|||
#import <mach-o/dyld.h>
|
||||
#import <mach-o/getsect.h>
|
||||
|
||||
#import "RCTContextExecutor.h"
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTEventDispatcher.h"
|
||||
#import "RCTJavaScriptLoader.h"
|
||||
#import "RCTKeyCommands.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTRootView.h"
|
||||
#import "RCTSparseArray.h"
|
||||
#import "RCTUtils.h"
|
||||
#import "RCTWebViewExecutor.h"
|
||||
|
||||
/**
|
||||
* Must be kept in sync with `MessageQueue.js`.
|
||||
|
@ -34,6 +39,8 @@ typedef NS_ENUM(NSUInteger, RCTBridgeFields) {
|
|||
RCTBridgeFieldFlushDateMillis
|
||||
};
|
||||
|
||||
NSString *const RCTReloadBridge = @"RCTReloadBridge";
|
||||
|
||||
/**
|
||||
* This function returns the module name for a given class.
|
||||
*/
|
||||
|
@ -125,6 +132,8 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
|
|||
NSString *_methodName;
|
||||
}
|
||||
|
||||
static Class _globalExecutorClass;
|
||||
|
||||
- (instancetype)initWithMethodName:(NSString *)methodName
|
||||
JSMethodName:(NSString *)JSMethodName
|
||||
{
|
||||
|
@ -497,33 +506,41 @@ static NSDictionary *RCTLocalModulesConfig()
|
|||
RCTSparseArray *_modulesByID;
|
||||
NSDictionary *_modulesByName;
|
||||
id<RCTJavaScriptExecutor> _javaScriptExecutor;
|
||||
Class _executorClass;
|
||||
NSString *_bundlePath;
|
||||
NSDictionary *_launchOptions;
|
||||
RCTBridgeModuleProviderBlock _moduleProvider;
|
||||
BOOL _loaded;
|
||||
}
|
||||
|
||||
static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
|
||||
- (instancetype)initWithBundlePath:(NSString *)bundlepath
|
||||
- (instancetype)initWithBundlePath:(NSString *)bundlePath
|
||||
moduleProvider:(RCTBridgeModuleProviderBlock)block
|
||||
launchOptions:(NSDictionary *)launchOptions
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self];
|
||||
_shadowQueue = dispatch_queue_create("com.facebook.React.ShadowQueue", DISPATCH_QUEUE_SERIAL);
|
||||
_bundlePath = bundlePath;
|
||||
_moduleProvider = block;
|
||||
_launchOptions = launchOptions;
|
||||
[self setUp];
|
||||
[self bindKeys];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setJavaScriptExecutor:(id<RCTJavaScriptExecutor>)executor
|
||||
{
|
||||
_javaScriptExecutor = executor;
|
||||
_latestJSExecutor = _javaScriptExecutor;
|
||||
[self setUp];
|
||||
}
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
Class executorClass = _executorClass ?: _globalExecutorClass ?: [RCTContextExecutor class];
|
||||
if ([NSStringFromClass(executorClass) isEqualToString:@"RCTWebViewExecutor"]) {
|
||||
_javaScriptExecutor = [[RCTWebViewExecutor alloc] initWithWebView:[[UIWebView alloc] init]];
|
||||
} else {
|
||||
_javaScriptExecutor = [[executorClass alloc] init];
|
||||
}
|
||||
_latestJSExecutor = _javaScriptExecutor;
|
||||
_eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self];
|
||||
_shadowQueue = dispatch_queue_create("com.facebook.ReactKit.ShadowQueue", DISPATCH_QUEUE_SERIAL);
|
||||
|
||||
// Register passed-in module instances
|
||||
NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init];
|
||||
for (id<RCTBridgeModule> module in _moduleProvider ? _moduleProvider() : nil) {
|
||||
|
@ -574,11 +591,73 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
dispatch_semaphore_signal(semaphore);
|
||||
}];
|
||||
|
||||
if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC)) != 0) {
|
||||
RCTLogError(@"JavaScriptExecutor took too long to inject JSON object");
|
||||
|
||||
if (_javaScriptExecutor == nil) {
|
||||
/**
|
||||
* HACK (tadeu): If it failed to connect to the debugger, set loaded to true so we can
|
||||
* reload
|
||||
*/
|
||||
_loaded = YES;
|
||||
} else if (_bundlePath != nil) { // Allow testing without a script
|
||||
RCTJavaScriptLoader *loader = [[RCTJavaScriptLoader alloc] initWithBridge:self];
|
||||
[loader loadBundleAtURL:[NSURL URLWithString:_bundlePath]
|
||||
onComplete:^(NSError *error) {
|
||||
_loaded = YES;
|
||||
if (error != nil) {
|
||||
NSArray *stack = [[error userInfo] objectForKey:@"stack"];
|
||||
if (stack) {
|
||||
[[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription] withStack:stack];
|
||||
} else {
|
||||
[[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription] withDetails:[error localizedFailureReason]];
|
||||
}
|
||||
} else {
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification
|
||||
object:self];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(reload)
|
||||
name:RCTReloadNotification
|
||||
object:nil];
|
||||
;
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)bindKeys
|
||||
{
|
||||
#if TARGET_IPHONE_SIMULATOR
|
||||
// Workaround around the first cmd+r not working: http://openradar.appspot.com/19613391
|
||||
// You can register just the cmd key and do nothing. This will trigger the bug and cmd+r
|
||||
// will work like a charm!
|
||||
[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@""
|
||||
modifierFlags:UIKeyModifierCommand
|
||||
action:^(UIKeyCommand *command) {
|
||||
// Do nothing
|
||||
}];
|
||||
|
||||
[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"r"
|
||||
modifierFlags:UIKeyModifierCommand
|
||||
action:^(UIKeyCommand *command) {
|
||||
[self reload];
|
||||
}];
|
||||
[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"n"
|
||||
modifierFlags:UIKeyModifierCommand
|
||||
action:^(UIKeyCommand *command) {
|
||||
_executorClass = Nil;
|
||||
[self reload];
|
||||
}];
|
||||
|
||||
[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"d"
|
||||
modifierFlags:UIKeyModifierCommand
|
||||
action:^(UIKeyCommand *command) {
|
||||
_executorClass = NSClassFromString(@"RCTWebSocketExecutor");
|
||||
if (!_executorClass) {
|
||||
RCTLogError(@"WebSocket debugger is not available. Did you forget to include RCTWebSocketExecutor?");
|
||||
}
|
||||
[self reload];
|
||||
}];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (NSDictionary *)modules
|
||||
{
|
||||
|
@ -602,6 +681,13 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
|
||||
- (void)invalidate
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
|
||||
// Wait for queued methods to finish
|
||||
dispatch_sync(self.shadowQueue, ^{
|
||||
// Make sure all dispatchers have been executed before continuing
|
||||
});
|
||||
|
||||
// Release executor
|
||||
if (_latestJSExecutor == _javaScriptExecutor) {
|
||||
_latestJSExecutor = nil;
|
||||
|
@ -609,11 +695,6 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
[_javaScriptExecutor invalidate];
|
||||
_javaScriptExecutor = nil;
|
||||
|
||||
// Wait for queued methods to finish
|
||||
dispatch_sync(self.shadowQueue, ^{
|
||||
// Make sure all dispatchers have been executed before continuing
|
||||
});
|
||||
|
||||
// Invalidate modules
|
||||
for (id target in _modulesByID.allObjects) {
|
||||
if ([target respondsToSelector:@selector(invalidate)]) {
|
||||
|
@ -624,6 +705,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
// Release modules (breaks retain cycle if module has strong bridge reference)
|
||||
_modulesByID = nil;
|
||||
_modulesByName = nil;
|
||||
_loaded = NO;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -647,9 +729,11 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
NSNumber *methodID = RCTLocalMethodIDs[moduleDotMethod];
|
||||
RCTAssert(methodID != nil, @"Method '%@' not registered.", moduleDotMethod);
|
||||
|
||||
if (self.loaded) {
|
||||
[self _invokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"callFunctionReturnFlushedQueue"
|
||||
arguments:@[moduleID, methodID, args ?: @[]]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete
|
||||
|
@ -793,6 +877,19 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
return YES;
|
||||
}
|
||||
|
||||
- (void)reload
|
||||
{
|
||||
if (_loaded) {
|
||||
// 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];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadViewsNotification
|
||||
object:self];
|
||||
}
|
||||
}
|
||||
|
||||
+ (BOOL)hasValidJSExecutor
|
||||
{
|
||||
return (_latestJSExecutor != nil && [_latestJSExecutor isValid]);
|
||||
|
|
|
@ -225,6 +225,7 @@ RCT_ENUM_CONVERTER(UIBarStyle, (@{
|
|||
NSString *key = aliases[alias]; \
|
||||
NSNumber *number = json[key]; \
|
||||
if (number) { \
|
||||
RCTLogWarn(@"Using deprecated '%@' property for '%s'. Use '%@' instead.", alias, #type, key); \
|
||||
((NSMutableDictionary *)json)[key] = number; \
|
||||
} \
|
||||
} \
|
||||
|
@ -245,9 +246,9 @@ RCT_ENUM_CONVERTER(UIBarStyle, (@{
|
|||
}
|
||||
|
||||
RCT_CUSTOM_CONVERTER(CGFloat, CGFloat, [self double:json])
|
||||
RCT_CGSTRUCT_CONVERTER(CGPoint, (@[@"x", @"y"]), nil)
|
||||
RCT_CGSTRUCT_CONVERTER(CGPoint, (@[@"x", @"y"]), (@{@"l": @"x", @"t": @"y"}))
|
||||
RCT_CGSTRUCT_CONVERTER(CGSize, (@[@"width", @"height"]), (@{@"w": @"width", @"h": @"height"}))
|
||||
RCT_CGSTRUCT_CONVERTER(CGRect, (@[@"x", @"y", @"width", @"height"]), (@{@"w": @"width", @"h": @"height"}))
|
||||
RCT_CGSTRUCT_CONVERTER(CGRect, (@[@"x", @"y", @"width", @"height"]), (@{@"l": @"x", @"t": @"y", @"w": @"width", @"h": @"height"}))
|
||||
RCT_CGSTRUCT_CONVERTER(UIEdgeInsets, (@[@"top", @"left", @"bottom", @"right"]), nil)
|
||||
|
||||
RCT_ENUM_CONVERTER(CGLineJoin, (@{
|
||||
|
|
|
@ -9,11 +9,11 @@
|
|||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class RCTRootView;
|
||||
@class RCTBridge;
|
||||
|
||||
@interface RCTDevMenu : NSObject
|
||||
|
||||
- (instancetype)initWithRootView:(RCTRootView *)rootView;
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
|
||||
- (void)show;
|
||||
|
||||
@end
|
||||
|
|
|
@ -11,28 +11,29 @@
|
|||
|
||||
#import "RCTRedBox.h"
|
||||
#import "RCTRootView.h"
|
||||
#import "RCTSourceCode.h"
|
||||
|
||||
@interface RCTDevMenu () <UIActionSheetDelegate> {
|
||||
BOOL _liveReload;
|
||||
}
|
||||
|
||||
@property (nonatomic, weak) RCTRootView *view;
|
||||
@property (nonatomic, weak) RCTBridge *bridge;
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTDevMenu
|
||||
|
||||
- (instancetype)initWithRootView:(RCTRootView *)rootView
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
||||
{
|
||||
if (self = [super init]) {
|
||||
self.view = rootView;
|
||||
_bridge = bridge;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)show
|
||||
{
|
||||
NSString *debugTitle = self.view.executorClass == nil ? @"Enable Debugging" : @"Disable Debugging";
|
||||
NSString *debugTitle = self.bridge.executorClass == Nil ? @"Enable Debugging" : @"Disable Debugging";
|
||||
NSString *liveReloadTitle = _liveReload ? @"Disable Live Reload" : @"Enable Live Reload";
|
||||
UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"React Native: Development"
|
||||
delegate:self
|
||||
|
@ -40,16 +41,16 @@
|
|||
destructiveButtonTitle:nil
|
||||
otherButtonTitles:@"Reload", debugTitle, liveReloadTitle, nil];
|
||||
actionSheet.actionSheetStyle = UIBarStyleBlack;
|
||||
[actionSheet showInView:self.view];
|
||||
[actionSheet showInView:[[[[UIApplication sharedApplication] keyWindow] rootViewController] view]];
|
||||
}
|
||||
|
||||
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
|
||||
{
|
||||
if (buttonIndex == 0) {
|
||||
[self.view reload];
|
||||
[self.bridge reload];
|
||||
} else if (buttonIndex == 1) {
|
||||
self.view.executorClass = self.view.executorClass == nil ? NSClassFromString(@"RCTWebSocketExecutor") : nil;
|
||||
[self.view reload];
|
||||
self.bridge.executorClass = self.bridge.executorClass == Nil ? NSClassFromString(@"RCTWebSocketExecutor") : nil;
|
||||
[self.bridge reload];
|
||||
} else if (buttonIndex == 2) {
|
||||
_liveReload = !_liveReload;
|
||||
[self _pollAndReload];
|
||||
|
@ -59,7 +60,8 @@
|
|||
- (void)_pollAndReload
|
||||
{
|
||||
if (_liveReload) {
|
||||
NSURL *url = [self.view scriptURL];
|
||||
RCTSourceCode *sourceCodeModule = self.bridge.modules[NSStringFromClass([RCTSourceCode class])];
|
||||
NSURL *url = sourceCodeModule.scriptURL;
|
||||
NSURL *longPollURL = [[NSURL alloc] initWithString:@"/onchange" relativeToURL:url];
|
||||
[self performSelectorInBackground:@selector(_checkForUpdates:) withObject:longPollURL];
|
||||
}
|
||||
|
@ -75,7 +77,7 @@
|
|||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (_liveReload && response.statusCode == 205) {
|
||||
[[RCTRedBox sharedInstance] dismiss];
|
||||
[self.view reload];
|
||||
[self.bridge reload];
|
||||
}
|
||||
[self _pollAndReload];
|
||||
});
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTJavaScriptExecutor.h"
|
||||
|
||||
@class RCTBridge;
|
||||
|
||||
/**
|
||||
* Class that allows easy embedding, loading, life-cycle management of a
|
||||
* JavaScript application inside of a native application.
|
||||
* TODO: Before loading new application source, publish global notification in
|
||||
* JavaScript so that applications can clean up resources. (launch blocker).
|
||||
* TODO: Incremental module loading. (low pri).
|
||||
*/
|
||||
@interface RCTJavaScriptLoader : NSObject
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (void)loadBundleAtURL:(NSURL *)moduleURL onComplete:(RCTJavaScriptCompleteBlock)onComplete;
|
||||
|
||||
@end
|
|
@ -0,0 +1,140 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import "RCTJavaScriptLoader.h"
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTInvalidating.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTRedBox.h"
|
||||
#import "RCTSourceCode.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
#define NO_REMOTE_MODULE @"Could not fetch module bundle %@. Ensure node server is running.\n\nIf it timed out, try reloading."
|
||||
#define NO_LOCAL_BUNDLE @"Could not load local bundle %@. Ensure file exists."
|
||||
|
||||
#define CACHE_DIR @"RCTJSBundleCache"
|
||||
|
||||
#pragma mark - Application Engine
|
||||
|
||||
/**
|
||||
* TODO:
|
||||
* - Add window resize rotation events matching the DOM API.
|
||||
* - Device pixel ration hooks.
|
||||
* - Source maps.
|
||||
*/
|
||||
@implementation RCTJavaScriptLoader
|
||||
{
|
||||
RCTBridge *_bridge;
|
||||
}
|
||||
|
||||
/**
|
||||
* `CADisplayLink` code copied from Ejecta but we've placed the JavaScriptCore
|
||||
* engine in its own dedicated thread.
|
||||
*
|
||||
* TODO: Try adding to the `RCTJavaScriptExecutor`'s thread runloop. Removes one
|
||||
* additional GCD dispatch per frame and likely makes it so that other UIThread
|
||||
* operations don't delay the dispatch (so we can begin working in JS much
|
||||
* faster.) Event handling must still be sent via a GCD dispatch, of course.
|
||||
*
|
||||
* We must add the display link to two runloops in order to get setTimeouts to
|
||||
* fire during scrolling. (`NSDefaultRunLoopMode` and `UITrackingRunLoopMode`)
|
||||
* TODO: We can invent a `requestAnimationFrame` and
|
||||
* `requestAvailableAnimationFrame` to control if callbacks can be fired during
|
||||
* an animation.
|
||||
* http://stackoverflow.com/questions/12622800/why-does-uiscrollview-pause-my-cadisplaylink
|
||||
*
|
||||
*/
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
||||
{
|
||||
RCTAssertMainThread();
|
||||
if (self = [super init]) {
|
||||
_bridge = bridge;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)loadBundleAtURL:(NSURL *)scriptURL onComplete:(void (^)(NSError *))onComplete
|
||||
{
|
||||
if (scriptURL == nil) {
|
||||
NSError *error = [NSError errorWithDomain:@"JavaScriptLoader"
|
||||
code:1
|
||||
userInfo:@{NSLocalizedDescriptionKey: @"No script URL provided"}];
|
||||
onComplete(error);
|
||||
return;
|
||||
} else if ([scriptURL isFileURL]) {
|
||||
NSString *bundlePath = [[NSBundle bundleForClass:[self class]] resourcePath];
|
||||
NSString *localPath = [scriptURL.absoluteString substringFromIndex:@"file://".length];
|
||||
|
||||
if (![localPath hasPrefix:bundlePath]) {
|
||||
NSString *absolutePath = [NSString stringWithFormat:@"%@/%@", bundlePath, localPath];
|
||||
scriptURL = [NSURL fileURLWithPath:absolutePath];
|
||||
}
|
||||
}
|
||||
|
||||
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:scriptURL completionHandler:
|
||||
^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||
|
||||
// Handle general request errors
|
||||
if (error) {
|
||||
if ([[error domain] isEqualToString:NSURLErrorDomain]) {
|
||||
NSDictionary *userInfo = @{
|
||||
NSLocalizedDescriptionKey: @"Could not connect to development server. Ensure node server is running - run 'npm start' from ReactKit root",
|
||||
NSLocalizedFailureReasonErrorKey: [error localizedDescription],
|
||||
NSUnderlyingErrorKey: error,
|
||||
};
|
||||
error = [NSError errorWithDomain:@"JSServer"
|
||||
code:error.code
|
||||
userInfo:userInfo];
|
||||
}
|
||||
onComplete(error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse response as text
|
||||
NSStringEncoding encoding = NSUTF8StringEncoding;
|
||||
if (response.textEncodingName != nil) {
|
||||
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
|
||||
if (cfEncoding != kCFStringEncodingInvalidId) {
|
||||
encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
|
||||
}
|
||||
}
|
||||
NSString *rawText = [[NSString alloc] initWithData:data encoding:encoding];
|
||||
|
||||
// Handle HTTP errors
|
||||
if ([response isKindOfClass:[NSHTTPURLResponse class]] && [(NSHTTPURLResponse *)response statusCode] != 200) {
|
||||
NSDictionary *userInfo;
|
||||
NSDictionary *errorDetails = RCTJSONParse(rawText, nil);
|
||||
if ([errorDetails isKindOfClass:[NSDictionary class]]) {
|
||||
userInfo = @{
|
||||
NSLocalizedDescriptionKey: errorDetails[@"message"] ?: @"No message provided",
|
||||
@"stack": @[@{
|
||||
@"methodName": errorDetails[@"description"] ?: @"",
|
||||
@"file": errorDetails[@"filename"] ?: @"",
|
||||
@"lineNumber": errorDetails[@"lineNumber"] ?: @0
|
||||
}]
|
||||
};
|
||||
} else {
|
||||
userInfo = @{NSLocalizedDescriptionKey: rawText};
|
||||
}
|
||||
error = [NSError errorWithDomain:@"JSServer"
|
||||
code:[(NSHTTPURLResponse *)response statusCode]
|
||||
userInfo:userInfo];
|
||||
|
||||
onComplete(error);
|
||||
return;
|
||||
}
|
||||
RCTSourceCode *sourceCodeModule = _bridge.modules[NSStringFromClass([RCTSourceCode class])];
|
||||
sourceCodeModule.scriptURL = scriptURL;
|
||||
sourceCodeModule.scriptText = rawText;
|
||||
|
||||
[_bridge enqueueApplicationScript:rawText url:scriptURL onComplete:^(NSError *_error) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
onComplete(_error);
|
||||
});
|
||||
}];
|
||||
}];
|
||||
|
||||
[task resume];
|
||||
}
|
||||
|
||||
@end
|
|
@ -11,18 +11,25 @@
|
|||
|
||||
#import "RCTBridge.h"
|
||||
|
||||
@interface RCTRootView : UIView<RCTInvalidating>
|
||||
extern NSString *const RCTJavaScriptDidLoadNotification;
|
||||
extern NSString *const RCTReloadNotification;
|
||||
extern NSString *const RCTReloadViewsNotification;
|
||||
|
||||
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
|
||||
moduleName:(NSString *)moduleName
|
||||
launchOptions:(NSDictionary *)launchOptions /* NS_DESIGNATED_INITIALIZER */;
|
||||
@interface RCTRootView : UIView <RCTInvalidating>
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
||||
moduleName:(NSString *)moduleName NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
* The URL of the bundled application script (required).
|
||||
* Setting this will clear the view contents, and trigger
|
||||
* an asynchronous load/download and execution of the script.
|
||||
* - Convenience initializer -
|
||||
* A bridge will be created internally.
|
||||
* This initializer is intended to be used when the app has a single RCTRootView,
|
||||
* otherwise create an `RCTBridge` and pass it in via `initWithBridge:moduleName:`
|
||||
* to all the instances.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly) NSURL *scriptURL;
|
||||
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
|
||||
moduleName:(NSString *)moduleName
|
||||
launchOptions:(NSDictionary *)launchOptions;
|
||||
|
||||
/**
|
||||
* The name of the JavaScript module to execute within the
|
||||
|
@ -32,12 +39,7 @@
|
|||
*/
|
||||
@property (nonatomic, copy, readonly) NSString *moduleName;
|
||||
|
||||
/**
|
||||
* A block that returns an array of pre-allocated modules. These
|
||||
* modules will take precedence over any automatically registered
|
||||
* modules of the same name.
|
||||
*/
|
||||
@property (nonatomic, copy, readonly) RCTBridgeModuleProviderBlock moduleProvider;
|
||||
@property (nonatomic, strong, readonly) RCTBridge *bridge;
|
||||
|
||||
/**
|
||||
* The default properties to apply to the view when the script bundle
|
||||
|
@ -64,6 +66,10 @@
|
|||
- (void)reload;
|
||||
+ (void)reloadAll;
|
||||
|
||||
@property (nonatomic, weak) UIViewController *backingViewController;
|
||||
|
||||
@property (nonatomic, strong, readonly) UIView *contentView;
|
||||
|
||||
- (void)startOrResetInteractionTiming;
|
||||
- (NSDictionary *)endAndResetInteractionTiming;
|
||||
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
|
||||
#import "RCTRootView.h"
|
||||
|
||||
#import <objc/runtime.h>
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTContextExecutor.h"
|
||||
#import "RCTDevMenu.h"
|
||||
|
@ -23,7 +25,9 @@
|
|||
#import "RCTWebViewExecutor.h"
|
||||
#import "UIView+React.h"
|
||||
|
||||
NSString *const RCTJavaScriptDidLoadNotification = @"RCTJavaScriptDidLoadNotification";
|
||||
NSString *const RCTReloadNotification = @"RCTReloadNotification";
|
||||
NSString *const RCTReloadViewsNotification = @"RCTReloadViewsNotification";
|
||||
|
||||
/**
|
||||
* HACK(t6568049) This should be removed soon, hiding to prevent people from
|
||||
|
@ -35,95 +39,113 @@ NSString *const RCTReloadNotification = @"RCTReloadNotification";
|
|||
|
||||
@end
|
||||
|
||||
@interface RCTUIManager (RCTRootView)
|
||||
|
||||
- (NSNumber *)allocateRootTag;
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTRootView
|
||||
{
|
||||
RCTDevMenu *_devMenu;
|
||||
RCTBridge *_bridge;
|
||||
RCTTouchHandler *_touchHandler;
|
||||
id<RCTJavaScriptExecutor> _executor;
|
||||
NSString *_moduleName;
|
||||
BOOL _registered;
|
||||
NSDictionary *_launchOptions;
|
||||
UIView *_contentView;
|
||||
}
|
||||
|
||||
static Class _globalExecutorClass;
|
||||
|
||||
+ (void)initialize
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
||||
moduleName:(NSString *)moduleName
|
||||
{
|
||||
RCTAssert(bridge, @"A bridge instance is required to create an RCTRootView");
|
||||
RCTAssert(moduleName, @"A moduleName is required to create an RCTRootView");
|
||||
|
||||
#if TARGET_IPHONE_SIMULATOR
|
||||
|
||||
// Register Cmd-R as a global refresh key
|
||||
[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"r"
|
||||
modifierFlags:UIKeyModifierCommand
|
||||
action:^(UIKeyCommand *command) {
|
||||
[self reloadAll];
|
||||
}];
|
||||
|
||||
// Cmd-D reloads using the web view executor, allows attaching from Safari dev tools.
|
||||
[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"d"
|
||||
modifierFlags:UIKeyModifierCommand
|
||||
action:^(UIKeyCommand *command) {
|
||||
_globalExecutorClass = NSClassFromString(@"RCTWebSocketExecutor");
|
||||
if (!_globalExecutorClass) {
|
||||
RCTLogError(@"WebSocket debugger is not available. Did you forget to include RCTWebSocketExecutor?");
|
||||
}
|
||||
[self reloadAll];
|
||||
}];
|
||||
|
||||
if ((self = [super init])) {
|
||||
#ifdef DEBUG
|
||||
_enableDevMenu = YES;
|
||||
#endif
|
||||
|
||||
_bridge = bridge;
|
||||
_moduleName = moduleName;
|
||||
self.backgroundColor = [UIColor whiteColor];
|
||||
[self setUp];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
|
||||
moduleName:(NSString *)moduleName
|
||||
launchOptions:(NSDictionary *)launchOptions
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
RCTAssert(bundleURL, @"A bundleURL is required to create an RCTRootView");
|
||||
RCTAssert(moduleName, @"A bundleURL is required to create an RCTRootView");
|
||||
_moduleName = moduleName;
|
||||
_launchOptions = launchOptions;
|
||||
[self setUp];
|
||||
[self setScriptURL:bundleURL];
|
||||
}
|
||||
return self;
|
||||
RCTBridge *bridge = [[RCTBridge alloc] initWithBundlePath:bundleURL.absoluteString
|
||||
moduleProvider:nil
|
||||
launchOptions:launchOptions];
|
||||
return [self initWithBridge:bridge
|
||||
moduleName:moduleName];
|
||||
}
|
||||
|
||||
/**
|
||||
* HACK(t6568049) Private constructor for testing purposes
|
||||
*/
|
||||
- (instancetype)_initWithBundleURL:(NSURL *)bundleURL
|
||||
moduleName:(NSString *)moduleName
|
||||
launchOptions:(NSDictionary *)launchOptions
|
||||
moduleProvider:(RCTBridgeModuleProviderBlock)moduleProvider
|
||||
- (void)dealloc
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_moduleProvider = moduleProvider;
|
||||
_moduleName = moduleName;
|
||||
_launchOptions = launchOptions;
|
||||
[self setUp];
|
||||
[self setScriptURL:bundleURL];
|
||||
}
|
||||
return self;
|
||||
[self tearDown];
|
||||
}
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
// Every root view that is created must have a unique react tag.
|
||||
// Numbering of these tags goes from 1, 11, 21, 31, etc
|
||||
static NSInteger rootViewTag = 1;
|
||||
self.reactTag = @(rootViewTag);
|
||||
#ifdef DEBUG
|
||||
self.enableDevMenu = YES;
|
||||
#endif
|
||||
self.backgroundColor = [UIColor whiteColor];
|
||||
rootViewTag += 10;
|
||||
if (!_registered) {
|
||||
/**
|
||||
* Every root view that is created must have a unique react tag.
|
||||
* Numbering of these tags goes from 1, 11, 21, 31, etc
|
||||
*
|
||||
* NOTE: Since the bridge persists, the RootViews might be reused, so now
|
||||
* the react tag is assigned every time we load new content.
|
||||
*/
|
||||
_contentView = [[UIView alloc] init];
|
||||
_contentView.reactTag = [_bridge.uiManager allocateRootTag];
|
||||
_touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge];
|
||||
[_contentView addGestureRecognizer:_touchHandler];
|
||||
[self addSubview:_contentView];
|
||||
|
||||
// Add reload observer
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(reload)
|
||||
name:RCTReloadNotification
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(reload)
|
||||
name:RCTReloadViewsNotification
|
||||
object:_bridge];
|
||||
if (_bridge.loaded) {
|
||||
[self bundleFinishedLoading];
|
||||
} else {
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(bundleFinishedLoading)
|
||||
name:RCTJavaScriptDidLoadNotification
|
||||
object:_bridge];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)tearDown
|
||||
{
|
||||
if (_registered) {
|
||||
_registered = NO;
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
[_contentView removeGestureRecognizer:_touchHandler];
|
||||
[_contentView removeFromSuperview];
|
||||
[_touchHandler invalidate];
|
||||
[_bridge enqueueJSCall:@"ReactIOS.unmountComponentAtNodeAndRemoveContainer"
|
||||
args:@[_contentView.reactTag]];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isValid
|
||||
{
|
||||
return _registered;
|
||||
}
|
||||
|
||||
- (void)invalidate
|
||||
{
|
||||
[self tearDown];
|
||||
}
|
||||
|
||||
- (UIViewController *)backingViewController {
|
||||
return _backingViewController ?: [super backingViewController];
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeFirstResponder
|
||||
|
@ -135,7 +157,7 @@ static Class _globalExecutorClass;
|
|||
{
|
||||
if (motion == UIEventSubtypeMotionShake && self.enableDevMenu) {
|
||||
if (!_devMenu) {
|
||||
_devMenu = [[RCTDevMenu alloc] initWithRootView:self];
|
||||
_devMenu = [[RCTDevMenu alloc] initWithBridge:self.bridge];
|
||||
}
|
||||
[_devMenu show];
|
||||
}
|
||||
|
@ -149,194 +171,51 @@ static Class _globalExecutorClass;
|
|||
];
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
- (void)bundleFinishedLoading
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
|
||||
[_bridge enqueueJSCall:@"ReactIOS.unmountComponentAtNodeAndRemoveContainer"
|
||||
args:@[self.reactTag]];
|
||||
[self invalidate];
|
||||
}
|
||||
|
||||
#pragma mark - RCTInvalidating
|
||||
|
||||
- (BOOL)isValid
|
||||
{
|
||||
return [_bridge isValid];
|
||||
}
|
||||
|
||||
- (void)invalidate
|
||||
{
|
||||
// Clear view
|
||||
[self.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
|
||||
|
||||
[self removeGestureRecognizer:_touchHandler];
|
||||
[_touchHandler invalidate];
|
||||
[_executor invalidate];
|
||||
|
||||
// TODO: eventually we'll want to be able to share the bridge between
|
||||
// multiple rootviews, in which case we'll need to move this elsewhere
|
||||
[_bridge invalidate];
|
||||
}
|
||||
|
||||
#pragma mark Bundle loading
|
||||
|
||||
- (void)bundleFinishedLoading:(NSError *)error
|
||||
{
|
||||
if (error != nil) {
|
||||
NSArray *stack = [[error userInfo] objectForKey:@"stack"];
|
||||
if (stack) {
|
||||
[[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription] withStack:stack];
|
||||
} else {
|
||||
[[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription] withDetails:[error localizedFailureReason]];
|
||||
}
|
||||
} else {
|
||||
|
||||
[_bridge.uiManager registerRootView:self];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
_registered = YES;
|
||||
|
||||
NSString *moduleName = _moduleName ?: @"";
|
||||
NSDictionary *appParameters = @{
|
||||
@"rootTag": self.reactTag,
|
||||
@"initialProps": self.initialProperties ?: @{},
|
||||
};
|
||||
@"rootTag": _contentView.reactTag,
|
||||
@"initialProps": self.initialProperties ?: @{},
|
||||
};
|
||||
[_bridge.uiManager registerRootView:_contentView];
|
||||
[_bridge enqueueJSCall:@"AppRegistry.runApplication"
|
||||
args:@[moduleName, appParameters]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)loadBundle
|
||||
{
|
||||
[self invalidate];
|
||||
|
||||
if (!_scriptURL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean up
|
||||
[self removeGestureRecognizer:_touchHandler];
|
||||
[_touchHandler invalidate];
|
||||
[_executor invalidate];
|
||||
[_bridge invalidate];
|
||||
|
||||
_registered = NO;
|
||||
|
||||
// Choose local executor if specified, followed by global, followed by default
|
||||
_executor = [[_executorClass ?: _globalExecutorClass ?: [RCTContextExecutor class] alloc] init];
|
||||
|
||||
/**
|
||||
* HACK(t6568049) Most of the properties passed into the bridge are not used
|
||||
* right now but it'll be changed soon so it's here for convenience.
|
||||
*/
|
||||
_bridge = [[RCTBridge alloc] initWithBundlePath:_scriptURL.absoluteString
|
||||
moduleProvider:_moduleProvider
|
||||
launchOptions:_launchOptions];
|
||||
[_bridge setJavaScriptExecutor:_executor];
|
||||
|
||||
_touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge];
|
||||
[self addGestureRecognizer:_touchHandler];
|
||||
|
||||
// Load the bundle
|
||||
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:_scriptURL completionHandler:
|
||||
^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||
|
||||
// Handle general request errors
|
||||
if (error) {
|
||||
if ([[error domain] isEqualToString:NSURLErrorDomain]) {
|
||||
NSDictionary *userInfo = @{
|
||||
NSLocalizedDescriptionKey: @"Could not connect to development server. Ensure node server is running - run 'npm start' from React root",
|
||||
NSLocalizedFailureReasonErrorKey: [error localizedDescription],
|
||||
NSUnderlyingErrorKey: error,
|
||||
};
|
||||
error = [NSError errorWithDomain:@"JSServer"
|
||||
code:error.code
|
||||
userInfo:userInfo];
|
||||
}
|
||||
[self bundleFinishedLoading:error];
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse response as text
|
||||
NSStringEncoding encoding = NSUTF8StringEncoding;
|
||||
if (response.textEncodingName != nil) {
|
||||
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
|
||||
if (cfEncoding != kCFStringEncodingInvalidId) {
|
||||
encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
|
||||
}
|
||||
}
|
||||
NSString *rawText = [[NSString alloc] initWithData:data encoding:encoding];
|
||||
|
||||
// Handle HTTP errors
|
||||
if ([response isKindOfClass:[NSHTTPURLResponse class]] && [(NSHTTPURLResponse *)response statusCode] != 200) {
|
||||
NSDictionary *userInfo;
|
||||
NSDictionary *errorDetails = RCTJSONParse(rawText, nil);
|
||||
if ([errorDetails isKindOfClass:[NSDictionary class]]) {
|
||||
userInfo = @{
|
||||
NSLocalizedDescriptionKey: errorDetails[@"message"] ?: @"No message provided",
|
||||
@"stack": @[@{
|
||||
@"methodName": errorDetails[@"description"] ?: @"",
|
||||
@"file": errorDetails[@"filename"] ?: @"",
|
||||
@"lineNumber": errorDetails[@"lineNumber"] ?: @0
|
||||
}]
|
||||
};
|
||||
} else {
|
||||
userInfo = @{NSLocalizedDescriptionKey: rawText};
|
||||
}
|
||||
error = [NSError errorWithDomain:@"JSServer"
|
||||
code:[(NSHTTPURLResponse *)response statusCode]
|
||||
userInfo:userInfo];
|
||||
|
||||
[self bundleFinishedLoading:error];
|
||||
return;
|
||||
}
|
||||
if (!_bridge.isValid) {
|
||||
return; // Bridge was invalidated in the meanwhile
|
||||
}
|
||||
|
||||
// Success!
|
||||
RCTSourceCode *sourceCodeModule = _bridge.modules[NSStringFromClass([RCTSourceCode class])];
|
||||
sourceCodeModule.scriptURL = _scriptURL;
|
||||
sourceCodeModule.scriptText = rawText;
|
||||
|
||||
[_bridge enqueueApplicationScript:rawText url:_scriptURL onComplete:^(NSError *_error) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (_bridge.isValid) {
|
||||
[self bundleFinishedLoading:_error];
|
||||
}
|
||||
});
|
||||
}];
|
||||
|
||||
}];
|
||||
|
||||
[task resume];
|
||||
}
|
||||
|
||||
- (void)setScriptURL:(NSURL *)scriptURL
|
||||
{
|
||||
if ([_scriptURL isEqual:scriptURL]) {
|
||||
return;
|
||||
}
|
||||
|
||||
_scriptURL = scriptURL;
|
||||
[self loadBundle];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
_contentView.frame = self.bounds;
|
||||
if (_registered) {
|
||||
[_bridge.uiManager setFrame:self.frame forRootView:self];
|
||||
[_bridge.uiManager setFrame:self.frame forRootView:_contentView];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setFrame:(CGRect)frame
|
||||
{
|
||||
[super setFrame:frame];
|
||||
_contentView.frame = self.bounds;
|
||||
}
|
||||
|
||||
- (void)reload
|
||||
{
|
||||
[self loadBundle];
|
||||
[self tearDown];
|
||||
[self setUp];
|
||||
}
|
||||
|
||||
+ (void)reloadAll
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification
|
||||
object:self];
|
||||
}
|
||||
|
||||
- (NSNumber *)reactTag
|
||||
{
|
||||
return _contentView.reactTag;
|
||||
}
|
||||
|
||||
- (void)startOrResetInteractionTiming
|
||||
|
@ -350,3 +229,14 @@ static Class _globalExecutorClass;
|
|||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTUIManager (RCTRootView)
|
||||
|
||||
- (NSNumber *)allocateRootTag
|
||||
{
|
||||
NSNumber *rootTag = objc_getAssociatedObject(self, _cmd) ?: @1;
|
||||
objc_setAssociatedObject(self, _cmd, @(rootTag.integerValue + 10), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
return rootTag;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
#import <JavaScriptCore/JavaScriptCore.h>
|
||||
|
||||
#import "RCTJavaScriptExecutor.h"
|
||||
#import "../Base/RCTJavaScriptExecutor.h"
|
||||
|
||||
// TODO (#5906496): Might RCTJSCoreExecutor be a better name for this?
|
||||
|
||||
|
|
|
@ -30,8 +30,7 @@
|
|||
@property (nonatomic, readwrite, weak) id<UIScrollViewDelegate> nativeMainScrollDelegate;
|
||||
|
||||
/**
|
||||
* Register a root view with the RCTUIManager. Theoretically, a single manager
|
||||
* can support multiple root views, however this feature is not currently exposed.
|
||||
* Register a root view with the RCTUIManager.
|
||||
*/
|
||||
- (void)registerRootView:(UIView *)rootView;
|
||||
|
||||
|
|
|
@ -192,9 +192,10 @@ static UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimatio
|
|||
NSMutableDictionary *_defaultShadowViews; // RCT thread only
|
||||
NSMutableDictionary *_defaultViews; // Main thread only
|
||||
NSDictionary *_viewManagers;
|
||||
NSUInteger _rootTag;
|
||||
}
|
||||
|
||||
@synthesize bridge =_bridge;
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
/**
|
||||
* This function derives the view name automatically
|
||||
|
@ -239,6 +240,7 @@ static NSString *RCTViewNameForModuleName(NSString *moduleName)
|
|||
// Internal resources
|
||||
_pendingUIBlocks = [[NSMutableArray alloc] init];
|
||||
_rootViewTags = [[NSMutableSet alloc] init];
|
||||
_rootTag = 1;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
@ -259,6 +261,7 @@ static NSString *RCTViewNameForModuleName(NSString *moduleName)
|
|||
|
||||
_bridge = bridge;
|
||||
_shadowQueue = _bridge.shadowQueue;
|
||||
_shadowViewRegistry = [[RCTSparseArray alloc] init];
|
||||
|
||||
// Get view managers from bridge
|
||||
NSMutableDictionary *viewManagers = [[NSMutableDictionary alloc] init];
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
13E067561A70F44B002CDEE1 /* RCTViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E0674E1A70F44B002CDEE1 /* RCTViewManager.m */; };
|
||||
13E067571A70F44B002CDEE1 /* RCTView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067501A70F44B002CDEE1 /* RCTView.m */; };
|
||||
13E067591A70F44B002CDEE1 /* UIView+React.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067541A70F44B002CDEE1 /* UIView+React.m */; };
|
||||
14200DAA1AC179B3008EE6BA /* RCTJavaScriptLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 14200DA91AC179B3008EE6BA /* RCTJavaScriptLoader.m */; };
|
||||
14435CE51AAC4AE100FC20F4 /* RCTMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 14435CE21AAC4AE100FC20F4 /* RCTMap.m */; };
|
||||
14435CE61AAC4AE100FC20F4 /* RCTMapManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14435CE41AAC4AE100FC20F4 /* RCTMapManager.m */; };
|
||||
14F3620D1AABD06A001CE568 /* RCTSwitch.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F362081AABD06A001CE568 /* RCTSwitch.m */; };
|
||||
|
@ -146,6 +147,8 @@
|
|||
13E067531A70F44B002CDEE1 /* UIView+React.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+React.h"; sourceTree = "<group>"; };
|
||||
13E067541A70F44B002CDEE1 /* UIView+React.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+React.m"; sourceTree = "<group>"; };
|
||||
13EFFCCF1A98E6FE002607DC /* RCTJSMethodRegistrar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTJSMethodRegistrar.h; sourceTree = "<group>"; };
|
||||
14200DA81AC179B3008EE6BA /* RCTJavaScriptLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTJavaScriptLoader.h; sourceTree = "<group>"; };
|
||||
14200DA91AC179B3008EE6BA /* RCTJavaScriptLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTJavaScriptLoader.m; sourceTree = "<group>"; };
|
||||
14435CE11AAC4AE100FC20F4 /* RCTMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMap.h; sourceTree = "<group>"; };
|
||||
14435CE21AAC4AE100FC20F4 /* RCTMap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMap.m; sourceTree = "<group>"; };
|
||||
14435CE31AAC4AE100FC20F4 /* RCTMapManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMapManager.h; sourceTree = "<group>"; };
|
||||
|
@ -348,6 +351,8 @@
|
|||
83CBBA491A601E3B00E9B192 /* Base */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
14200DA81AC179B3008EE6BA /* RCTJavaScriptLoader.h */,
|
||||
14200DA91AC179B3008EE6BA /* RCTJavaScriptLoader.m */,
|
||||
83CBBA4A1A601E3B00E9B192 /* RCTAssert.h */,
|
||||
83CBBA4B1A601E3B00E9B192 /* RCTAssert.m */,
|
||||
83CBBA5E1A601EAA00E9B192 /* RCTBridge.h */,
|
||||
|
@ -473,6 +478,7 @@
|
|||
13E067561A70F44B002CDEE1 /* RCTViewManager.m in Sources */,
|
||||
58C571C11AA56C1900CDF9C8 /* RCTDatePickerManager.m in Sources */,
|
||||
13B080061A6947C200A75B9A /* RCTScrollViewManager.m in Sources */,
|
||||
14200DAA1AC179B3008EE6BA /* RCTJavaScriptLoader.m in Sources */,
|
||||
137327EA1AA5CF210034F82E /* RCTTabBarManager.m in Sources */,
|
||||
13B080261A694A8400A75B9A /* RCTWrapperViewController.m in Sources */,
|
||||
13B080051A6947C200A75B9A /* RCTScrollView.m in Sources */,
|
||||
|
|
|
@ -31,19 +31,20 @@
|
|||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
systemIcons = @{
|
||||
@"more": @(UITabBarSystemItemMore),
|
||||
@"favorites": @(UITabBarSystemItemFavorites),
|
||||
@"featured": @(UITabBarSystemItemFeatured),
|
||||
@"topRated": @(UITabBarSystemItemTopRated),
|
||||
@"recents": @(UITabBarSystemItemRecents),
|
||||
@"contacts": @(UITabBarSystemItemContacts),
|
||||
@"history": @(UITabBarSystemItemHistory),
|
||||
@"bookmarks": @(UITabBarSystemItemBookmarks),
|
||||
@"search": @(UITabBarSystemItemSearch),
|
||||
@"downloads": @(UITabBarSystemItemDownloads),
|
||||
@"mostRecent": @(UITabBarSystemItemMostRecent),
|
||||
@"mostViewed": @(UITabBarSystemItemMostViewed),
|
||||
};
|
||||
@"bookmarks": @(UITabBarSystemItemBookmarks),
|
||||
@"contacts": @(UITabBarSystemItemContacts),
|
||||
@"downloads": @(UITabBarSystemItemDownloads),
|
||||
@"favorites": @(UITabBarSystemItemFavorites),
|
||||
@"featured": @(UITabBarSystemItemFeatured),
|
||||
@"history": @(UITabBarSystemItemHistory),
|
||||
@"more": @(UITabBarSystemItemMore),
|
||||
@"most-recent": @(UITabBarSystemItemMostRecent),
|
||||
@"most-viewed": @(UITabBarSystemItemMostViewed),
|
||||
@"recents": @(UITabBarSystemItemRecents),
|
||||
@"search": @(UITabBarSystemItemSearch),
|
||||
@"top-rated": @(UITabBarSystemItemTopRated),
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
// Update icon
|
||||
|
|
|
@ -16,6 +16,20 @@
|
|||
|
||||
static const RCTBorderSide RCTBorderSideCount = 4;
|
||||
|
||||
static UIView *RCTViewHitTest(UIView *view, CGPoint point, UIEvent *event)
|
||||
{
|
||||
for (UIView *subview in [view.subviews reverseObjectEnumerator]) {
|
||||
if (!subview.isHidden && subview.isUserInteractionEnabled && subview.alpha > 0) {
|
||||
CGPoint convertedPoint = [subview convertPoint:point fromView:view];
|
||||
UIView *subviewHitTestView = [subview hitTest:convertedPoint withEvent:event];
|
||||
if (subviewHitTestView != nil) {
|
||||
return subviewHitTestView;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
@implementation UIView (RCTViewUnmounting)
|
||||
|
||||
- (void)react_remountAllSubviews
|
||||
|
@ -120,20 +134,11 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
|
|||
case RCTPointerEventsNone:
|
||||
return nil;
|
||||
case RCTPointerEventsUnspecified:
|
||||
return [super hitTest:point withEvent:event];
|
||||
return RCTViewHitTest(self, point, event) ?: [super hitTest:point withEvent:event];
|
||||
case RCTPointerEventsBoxOnly:
|
||||
return [super hitTest:point withEvent:event] ? self: nil;
|
||||
case RCTPointerEventsBoxNone:
|
||||
for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
|
||||
if (!subview.isHidden && subview.isUserInteractionEnabled && subview.alpha > 0) {
|
||||
CGPoint convertedPoint = [subview convertPoint:point fromView:self];
|
||||
UIView *subviewHitTestView = [subview hitTest:convertedPoint withEvent:event];
|
||||
if (subviewHitTestView != nil) {
|
||||
return subviewHitTestView;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
return RCTViewHitTest(self, point, event);
|
||||
default:
|
||||
RCTLogError(@"Invalid pointer-events specified %zd on %@", _pointerEvents, self);
|
||||
return [super hitTest:point withEvent:event];
|
||||
|
|
|
@ -42,9 +42,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"connect": "2.8.3",
|
||||
"jstransform": "10.0.1",
|
||||
"jstransform": "10.1.0",
|
||||
"react-timer-mixin": "^0.13.1",
|
||||
"react-tools": "0.13.0-rc2",
|
||||
"react-tools": "0.13.1",
|
||||
"rebound": "^0.0.12",
|
||||
"source-map": "0.1.31",
|
||||
"stacktrace-parser": "0.1.1",
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var exec = require('child_process').exec;
|
||||
var http = require('http');
|
||||
|
||||
if (!fs.existsSync(path.resolve(__dirname, '..', 'node_modules'))) {
|
||||
console.log(
|
||||
|
@ -21,11 +23,9 @@ if (!fs.existsSync(path.resolve(__dirname, '..', 'node_modules'))) {
|
|||
process.exit();
|
||||
}
|
||||
|
||||
var exec = require('child_process').exec;
|
||||
var connect = require('connect');
|
||||
var ReactPackager = require('./react-packager');
|
||||
var blacklist = require('./blacklist.js');
|
||||
var connect = require('connect');
|
||||
var http = require('http');
|
||||
var launchEditor = require('./launchEditor.js');
|
||||
var parseCommandLine = require('./parseCommandLine.js');
|
||||
var webSocketProxy = require('./webSocketProxy.js');
|
||||
|
|
|
@ -64,7 +64,10 @@ FileWatcher.prototype.end = function() {
|
|||
|
||||
function createWatcher(rootConfig) {
|
||||
return detectingWatcherClass.then(function(Watcher) {
|
||||
var watcher = new Watcher(rootConfig.dir, rootConfig.globs);
|
||||
var watcher = new Watcher(rootConfig.dir, {
|
||||
glob: rootConfig.globs,
|
||||
dot: false,
|
||||
});
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
var rejectTimeout = setTimeout(function() {
|
||||
|
|
Loading…
Reference in New Issue