// Copyright 2004-present Facebook. All Rights Reserved. #import "RCTRootView.h" #import "RCTBridge.h" #import "RCTContextExecutor.h" #import "RCTEventDispatcher.h" #import "RCTJavaScriptAppEngine.h" #import "RCTTouchHandler.h" #import "RCTUIManager.h" #import "RCTUtils.h" #import "RCTViewManager.h" #import "RCTWebViewExecutor.h" #import "UIView+ReactKit.h" #import "RCTKeyCommands.h" NSString *const RCTRootViewReloadNotification = @"RCTRootViewReloadNotification"; @implementation RCTRootView { RCTBridge *_bridge; RCTJavaScriptAppEngine *_appEngine; RCTTouchHandler *_touchHandler; } static BOOL _useWebExec; + (void)initialize { #if DEBUG // 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) { _useWebExec = YES; [self reloadAll]; }]; #endif } - (id)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (!self) return nil; [self setUp]; return self; } - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (!self) return nil; [self setUp]; return self; } - (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); rootViewTag += 10; // Add reload observer [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reload) name:RCTRootViewReloadNotification object:nil]; self.backgroundColor = [UIColor whiteColor]; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (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 registerRootView:self]; NSString *moduleName = _moduleName ?: @""; NSDictionary *appParameters = @{ @"rootTag": self.reactTag ?: @0, @"initialProps": self.initialProperties ?: @{}, }; [_bridge enqueueJSCall:@"Bundler.runApplication" args:@[moduleName, appParameters]]; } } - (void)loadBundle { // Clear view [self.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)]; if (!_scriptURL) { return; } __weak typeof(self) weakSelf = self; RCTJavaScriptCompleteBlock callback = ^(NSError *error) { dispatch_async(dispatch_get_main_queue(), ^{ [weakSelf bundleFinishedLoading:error]; }); }; [_executor invalidate]; [_bridge invalidate]; if (!_useWebExec) { _executor = [[RCTContextExecutor alloc] init]; } else { _executor = [[RCTWebViewExecutor alloc] init]; } _bridge = [[RCTBridge alloc] initWithJavaScriptExecutor:_executor]; _appEngine = [[RCTJavaScriptAppEngine alloc] initWithBridge:_bridge]; _touchHandler = [[RCTTouchHandler alloc] initWithEventDispatcher:_bridge.eventDispatcher rootView:self]; [_appEngine loadBundleAtURL:_scriptURL useCache:NO onComplete:callback]; } - (void)setScriptURL:(NSURL *)scriptURL { if ([_scriptURL isEqual:scriptURL]) { return; } _scriptURL = scriptURL; [self loadBundle]; } - (void)setExecutor:(id)executor { RCTAssert(!_bridge, @"You may only change the Javascript Executor prior to loading a script bundle."); _executor = executor; } - (BOOL)isReactRootView { return YES; } - (void)reload { [RCTJavaScriptAppEngine resetCacheForBundleAtURL:_scriptURL]; [self loadBundle]; } + (void)reloadAll { [[NSNotificationCenter defaultCenter] postNotificationName:RCTRootViewReloadNotification object:nil]; } @end