diff --git a/Examples/UIExplorer/Navigator/JumpingNavSample.js b/Examples/UIExplorer/Navigator/JumpingNavSample.js
index 7b0fa2122..b38d9291d 100644
--- a/Examples/UIExplorer/Navigator/JumpingNavSample.js
+++ b/Examples/UIExplorer/Navigator/JumpingNavSample.js
@@ -55,29 +55,25 @@ class JumpingNavBar extends React.Component {
render() {
return (
-
+
{ this.props.onTabIndex(0); }}
- children={}
- />
+ onPress={() => { this.props.onTabIndex(0); }}>
+
+
{ this.props.onTabIndex(1); }}
- children={}
- />
+ onPress={() => { this.props.onTabIndex(1); }}>
+
+
{ this.props.onTabIndex(2); }}
- children={}
- />
+ onPress={() => { this.props.onTabIndex(2); }}>
+
+
);
diff --git a/Examples/UIExplorer/TabBarIOSExample.js b/Examples/UIExplorer/TabBarIOSExample.js
index 815c07d89..a8f913a07 100644
--- a/Examples/UIExplorer/TabBarIOSExample.js
+++ b/Examples/UIExplorer/TabBarIOSExample.js
@@ -22,9 +22,8 @@ var {
Text,
View,
} = React;
-var TabBarItemIOS = TabBarIOS.Item;
-var TabBarExample = React.createClass({
+var TabBarExample = React.createClass({
statics: {
title: '',
description: 'Tab-based navigation.'
@@ -42,19 +41,16 @@ var TabBarExample = React.createClass({
return (
{pageText}
- {this.state.presses} re-renders of this tab
+ {this.state.presses} re-renders of the More tab
);
},
render: function() {
return (
-
-
+ {
this.setState({
@@ -62,12 +58,10 @@ var TabBarExample = React.createClass({
});
}}>
{this._renderContent('#414A8C', 'Blue Tab')}
-
-
+ 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')}
-
-
+ {
this.setState({
@@ -89,7 +81,7 @@ var TabBarExample = React.createClass({
});
}}>
{this._renderContent('#21551C', 'Green Tab')}
-
+
);
},
@@ -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;
diff --git a/Libraries/Components/ScrollResponder.js b/Libraries/Components/ScrollResponder.js
index bb283e1ce..009bbebb4 100644
--- a/Libraries/Components/ScrollResponder.js
+++ b/Libraries/Components/ScrollResponder.js
@@ -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);
},
diff --git a/Libraries/Components/TabBarIOS/TabBarIOS.ios.js b/Libraries/Components/TabBarIOS/TabBarIOS.ios.js
index e508268c5..05ac37c74 100644
--- a/Libraries/Components/TabBarIOS/TabBarIOS.ios.js
+++ b/Libraries/Components/TabBarIOS/TabBarIOS.ios.js
@@ -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 (
diff --git a/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js b/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js
index 11b02ab39..f795ed7ce 100644
--- a/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js
+++ b/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js
@@ -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 = ;
}
+ 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 (
{tabContents}
diff --git a/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js b/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js
index 7dadeb5bb..d9118b748 100644
--- a/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js
+++ b/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js
@@ -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 };
diff --git a/Libraries/RCTTest/RCTTestModule.h b/Libraries/RCTTest/RCTTestModule.h
index 0f5adcd2c..21ed60c6b 100644
--- a/Libraries/RCTTest/RCTTestModule.h
+++ b/Libraries/RCTTest/RCTTestModule.h
@@ -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
diff --git a/Libraries/RCTTest/RCTTestRunner.m b/Libraries/RCTTest/RCTTestRunner.m
index 27aac48d9..8cb5169c3 100644
--- a/Libraries/RCTTest/RCTTestRunner.m
+++ b/Libraries/RCTTest/RCTTestRunner.m
@@ -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];
diff --git a/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m b/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m
index 933c49aed..ddf973b24 100644
--- a/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m
+++ b/Libraries/RCTWebSocketDebugger/RCTWebSocketExecutor.m
@@ -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;
diff --git a/Libraries/ReactIOS/ReactIOSDefaultInjection.js b/Libraries/ReactIOS/ReactIOSDefaultInjection.js
index a64236973..7b3df0d85 100644
--- a/Libraries/ReactIOS/ReactIOSDefaultInjection.js
+++ b/Libraries/ReactIOS/ReactIOSDefaultInjection.js
@@ -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);
}
diff --git a/React/Base/RCTBridge.h b/React/Base/RCTBridge.h
index 7aa28e49b..05767533d 100644
--- a/React/Base/RCTBridge.h
+++ b/React/Base/RCTBridge.h
@@ -7,6 +7,8 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
+#import
+
#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
+@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
diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m
index 148a5d933..1ad3d58b7 100644
--- a/React/Base/RCTBridge.m
+++ b/React/Base/RCTBridge.m
@@ -16,11 +16,16 @@
#import
#import
+#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 _javaScriptExecutor;
+ Class _executorClass;
+ NSString *_bundlePath;
+ NSDictionary *_launchOptions;
RCTBridgeModuleProviderBlock _moduleProvider;
+ BOOL _loaded;
}
static id _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)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 module in _moduleProvider ? _moduleProvider() : nil) {
@@ -574,11 +591,73 @@ static id _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 _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 _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 _latestJSExecutor;
// Release modules (breaks retain cycle if module has strong bridge reference)
_modulesByID = nil;
_modulesByName = nil;
+ _loaded = NO;
}
/**
@@ -647,9 +729,11 @@ static id _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 _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]);
diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m
index 3f8057d61..5fbc2d527 100644
--- a/React/Base/RCTConvert.m
+++ b/React/Base/RCTConvert.m
@@ -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, (@{
diff --git a/React/Base/RCTDevMenu.h b/React/Base/RCTDevMenu.h
index e7d3b8b30..a49e076e6 100644
--- a/React/Base/RCTDevMenu.h
+++ b/React/Base/RCTDevMenu.h
@@ -9,11 +9,11 @@
#import
-@class RCTRootView;
+@class RCTBridge;
@interface RCTDevMenu : NSObject
-- (instancetype)initWithRootView:(RCTRootView *)rootView;
+- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
- (void)show;
@end
diff --git a/React/Base/RCTDevMenu.m b/React/Base/RCTDevMenu.m
index 77ce73935..5fe58f608 100644
--- a/React/Base/RCTDevMenu.m
+++ b/React/Base/RCTDevMenu.m
@@ -11,28 +11,29 @@
#import "RCTRedBox.h"
#import "RCTRootView.h"
+#import "RCTSourceCode.h"
@interface RCTDevMenu () {
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];
});
diff --git a/React/Base/RCTJavaScriptLoader.h b/React/Base/RCTJavaScriptLoader.h
new file mode 100755
index 000000000..7c750c585
--- /dev/null
+++ b/React/Base/RCTJavaScriptLoader.h
@@ -0,0 +1,22 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#import
+
+#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
diff --git a/React/Base/RCTJavaScriptLoader.m b/React/Base/RCTJavaScriptLoader.m
new file mode 100755
index 000000000..1d61946b9
--- /dev/null
+++ b/React/Base/RCTJavaScriptLoader.m
@@ -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
diff --git a/React/Base/RCTRootView.h b/React/Base/RCTRootView.h
index e5776cc6f..b9e91b7a0 100644
--- a/React/Base/RCTRootView.h
+++ b/React/Base/RCTRootView.h
@@ -11,18 +11,25 @@
#import "RCTBridge.h"
-@interface RCTRootView : UIView
+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
+
+- (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;
diff --git a/React/Base/RCTRootView.m b/React/Base/RCTRootView.m
index ac39a9aed..9d913c048 100644
--- a/React/Base/RCTRootView.m
+++ b/React/Base/RCTRootView.m
@@ -9,6 +9,8 @@
#import "RCTRootView.h"
+#import
+
#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 _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
diff --git a/React/Executors/RCTContextExecutor.h b/React/Executors/RCTContextExecutor.h
index 159965a2f..6e62d87b6 100644
--- a/React/Executors/RCTContextExecutor.h
+++ b/React/Executors/RCTContextExecutor.h
@@ -9,7 +9,7 @@
#import
-#import "RCTJavaScriptExecutor.h"
+#import "../Base/RCTJavaScriptExecutor.h"
// TODO (#5906496): Might RCTJSCoreExecutor be a better name for this?
diff --git a/React/Modules/RCTUIManager.h b/React/Modules/RCTUIManager.h
index c70dda9c6..4f42cd0b7 100644
--- a/React/Modules/RCTUIManager.h
+++ b/React/Modules/RCTUIManager.h
@@ -30,8 +30,7 @@
@property (nonatomic, readwrite, weak) id 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;
diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m
index 82324f281..185982fdc 100644
--- a/React/Modules/RCTUIManager.m
+++ b/React/Modules/RCTUIManager.m
@@ -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];
diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj
index 537590ad1..f8dfd3862 100644
--- a/React/React.xcodeproj/project.pbxproj
+++ b/React/React.xcodeproj/project.pbxproj
@@ -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 = ""; };
13E067541A70F44B002CDEE1 /* UIView+React.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+React.m"; sourceTree = ""; };
13EFFCCF1A98E6FE002607DC /* RCTJSMethodRegistrar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTJSMethodRegistrar.h; sourceTree = ""; };
+ 14200DA81AC179B3008EE6BA /* RCTJavaScriptLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTJavaScriptLoader.h; sourceTree = ""; };
+ 14200DA91AC179B3008EE6BA /* RCTJavaScriptLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTJavaScriptLoader.m; sourceTree = ""; };
14435CE11AAC4AE100FC20F4 /* RCTMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMap.h; sourceTree = ""; };
14435CE21AAC4AE100FC20F4 /* RCTMap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMap.m; sourceTree = ""; };
14435CE31AAC4AE100FC20F4 /* RCTMapManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMapManager.h; sourceTree = ""; };
@@ -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 */,
diff --git a/React/Views/RCTTabBarItem.m b/React/Views/RCTTabBarItem.m
index 530fef893..967ae04af 100644
--- a/React/Views/RCTTabBarItem.m
+++ b/React/Views/RCTTabBarItem.m
@@ -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
diff --git a/React/Views/RCTView.m b/React/Views/RCTView.m
index febf56703..d40798302 100644
--- a/React/Views/RCTView.m
+++ b/React/Views/RCTView.m
@@ -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];
diff --git a/package.json b/package.json
index be08b782b..274a9ffdd 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/packager/packager.js b/packager/packager.js
index ad47d1d99..e2a32f2ca 100644
--- a/packager/packager.js
+++ b/packager/packager.js
@@ -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');
diff --git a/packager/react-packager/src/FileWatcher/index.js b/packager/react-packager/src/FileWatcher/index.js
index 6f451b482..9af96c144 100644
--- a/packager/react-packager/src/FileWatcher/index.js
+++ b/packager/react-packager/src/FileWatcher/index.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() {