diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index 7c9549b22..2d6bbab08 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -281,6 +281,9 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL }); } else { + [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptWillStartLoadingNotification + object:self + userInfo:@{ @"bridge": self }]; RCTProfileBeginEvent(); RCTPerformanceLoggerStart(RCTPLScriptDownload); RCTJavaScriptLoader *loader = [[RCTJavaScriptLoader alloc] initWithBridge:self]; diff --git a/React/Base/RCTBridge.h b/React/Base/RCTBridge.h index 72f34ae70..3721b1124 100644 --- a/React/Base/RCTBridge.h +++ b/React/Base/RCTBridge.h @@ -23,6 +23,11 @@ */ RCT_EXTERN NSString *const RCTReloadNotification; +/** + * This notification fires when the bridge starts loading. + */ +RCT_EXTERN NSString *const RCTJavaScriptWillStartLoadingNotification; + /** * This notification fires when the bridge has finished loading. */ diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index c670a528d..2520864d0 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -18,6 +18,7 @@ #import "RCTUtils.h" NSString *const RCTReloadNotification = @"RCTReloadNotification"; +NSString *const RCTJavaScriptWillStartLoadingNotification = @"RCTJavaScriptWillStartLoadingNotification"; NSString *const RCTJavaScriptDidLoadNotification = @"RCTJavaScriptDidLoadNotification"; NSString *const RCTJavaScriptDidFailToLoadNotification = @"RCTJavaScriptDidFailToLoadNotification"; NSString *const RCTDidCreateNativeModules = @"RCTDidCreateNativeModules"; diff --git a/React/Base/RCTDevLoadingView.h b/React/Base/RCTDevLoadingView.h new file mode 100644 index 000000000..05e749468 --- /dev/null +++ b/React/Base/RCTDevLoadingView.h @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import +#import + +@interface RCTDevLoadingView : NSObject + ++ (instancetype)sharedInstance; + +- (void)showWithURL:(NSURL *)URL; +- (void)hide; + +@end diff --git a/React/Base/RCTDevLoadingView.m b/React/Base/RCTDevLoadingView.m new file mode 100644 index 000000000..1bb832412 --- /dev/null +++ b/React/Base/RCTDevLoadingView.m @@ -0,0 +1,139 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import "RCTBridge.h" +#import "RCTDevLoadingView.h" +#import "RCTDefines.h" + +#if RCT_DEV + +@implementation RCTDevLoadingView +{ + UIWindow *_window; + UILabel *_label; + NSDate *_showDate; +} + +__attribute__((constructor)) +static void RCTDevLoadingViewSetup() +{ + [RCTDevLoadingView sharedInstance]; +} + ++ (instancetype)sharedInstance +{ + static dispatch_once_t onceToken; + static RCTDevLoadingView *instance; + dispatch_once(&onceToken, ^{ + instance = [[RCTDevLoadingView alloc] init]; + }); + return instance; +} + +- (instancetype)init +{ + if (self = [super init]) { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(willStartLoading:) + name:RCTJavaScriptWillStartLoadingNotification + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(didFinishLoading:) + name:RCTJavaScriptDidLoadNotification + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(didFinishLoading:) + name:RCTJavaScriptDidFailToLoadNotification + object:nil]; + } + return self; +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (void)willStartLoading:(NSNotification *)notification +{ + NSURL *url = [[notification object] bundleURL]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self showWithURL:url]; + }); +} + +- (void)didFinishLoading:(__unused NSNotification *)notification +{ + dispatch_async(dispatch_get_main_queue(), ^{ + [self hide]; + }); +} + +- (void)showWithURL:(NSURL *)URL +{ + _showDate = [NSDate date]; + if (!_window) { + CGFloat screenWidth = [[UIScreen mainScreen] bounds].size.width; + _window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, screenWidth, 22)]; + _window.backgroundColor = [UIColor blackColor]; + _window.windowLevel = UIWindowLevelStatusBar + 1; + + _label = [[UILabel alloc] initWithFrame:_window.bounds]; + _label.font = [UIFont systemFontOfSize:12.0]; + _label.textColor = [UIColor grayColor]; + _label.textAlignment = NSTextAlignmentCenter; + + [_window addSubview:_label]; + [_window makeKeyAndVisible]; + } + + NSString *source; + if ([URL isFileURL]) { + source = @"pre-bundled file"; + } else { + source = [NSString stringWithFormat:@"%@:%@", [URL host], [URL port]]; + } + + _label.text = [NSString stringWithFormat:@"Loading from %@...", source]; + _window.hidden = NO; +} + +- (void)hide +{ + const NSTimeInterval MIN_PRESENTED_TIME = 0.6; + NSTimeInterval presentedTime = [[NSDate date] timeIntervalSinceDate:_showDate]; + NSTimeInterval delay = MAX(0, MIN_PRESENTED_TIME - presentedTime); + + CGRect windowFrame = _window.frame; + [UIView animateWithDuration:0.25 + delay:delay + options:0 + animations:^{ + _window.frame = CGRectOffset(windowFrame, 0, -windowFrame.size.height); + } completion:^(__unused BOOL finished) { + _window.frame = windowFrame; + _window.hidden = YES; + }]; +} + +@end + +#else + +@implementation RCTDevLoadingView + ++ (instancetype)sharedInstance { return nil; } +- (void)showWithURL:(NSURL *)URL {} +- (void)hide {} + +@end + +#endif diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index bf69290a0..297747564 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 000E6CEB1AB0E980000CDF4D /* RCTSourceCode.m in Sources */ = {isa = PBXBuildFile; fileRef = 000E6CEA1AB0E980000CDF4D /* RCTSourceCode.m */; }; 00C1A2B31AC0B7E000E89A1C /* RCTDevMenu.m in Sources */ = {isa = PBXBuildFile; fileRef = 00C1A2B21AC0B7E000E89A1C /* RCTDevMenu.m */; }; + 00EAC16C1B62E089003B7CEE /* RCTDevLoadingView.m in Sources */ = {isa = PBXBuildFile; fileRef = 00EAC16B1B62E089003B7CEE /* RCTDevLoadingView.m */; }; 131B6AF41AF1093D00FFC3E0 /* RCTSegmentedControl.m in Sources */ = {isa = PBXBuildFile; fileRef = 131B6AF11AF1093D00FFC3E0 /* RCTSegmentedControl.m */; }; 131B6AF51AF1093D00FFC3E0 /* RCTSegmentedControlManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 131B6AF31AF1093D00FFC3E0 /* RCTSegmentedControlManager.m */; }; 13456E931ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m in Sources */ = {isa = PBXBuildFile; fileRef = 13456E921ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m */; }; @@ -98,6 +99,8 @@ 000E6CEA1AB0E980000CDF4D /* RCTSourceCode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSourceCode.m; sourceTree = ""; }; 00C1A2B11AC0B7E000E89A1C /* RCTDevMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDevMenu.h; sourceTree = ""; }; 00C1A2B21AC0B7E000E89A1C /* RCTDevMenu.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDevMenu.m; sourceTree = ""; }; + 00EAC16A1B62E089003B7CEE /* RCTDevLoadingView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDevLoadingView.h; sourceTree = ""; }; + 00EAC16B1B62E089003B7CEE /* RCTDevLoadingView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDevLoadingView.m; sourceTree = ""; }; 131B6AF01AF1093D00FFC3E0 /* RCTSegmentedControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSegmentedControl.h; sourceTree = ""; }; 131B6AF11AF1093D00FFC3E0 /* RCTSegmentedControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSegmentedControl.m; sourceTree = ""; }; 131B6AF21AF1093D00FFC3E0 /* RCTSegmentedControlManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSegmentedControlManager.h; sourceTree = ""; }; @@ -439,6 +442,8 @@ 13AF1F851AE6E777005F5298 /* RCTDefines.h */, 00C1A2B11AC0B7E000E89A1C /* RCTDevMenu.h */, 00C1A2B21AC0B7E000E89A1C /* RCTDevMenu.m */, + 00EAC16A1B62E089003B7CEE /* RCTDevLoadingView.h */, + 00EAC16B1B62E089003B7CEE /* RCTDevLoadingView.m */, 83CBBA651A601EF300E9B192 /* RCTEventDispatcher.h */, 83CBBA661A601EF300E9B192 /* RCTEventDispatcher.m */, 146459241B06C49500B389AA /* RCTFPSGraph.h */, @@ -585,6 +590,7 @@ 83CBBA511A601E3B00E9B192 /* RCTAssert.m in Sources */, 13AF20451AE707F9005F5298 /* RCTSlider.m in Sources */, 58114A501AAE93D500E7D092 /* RCTAsyncLocalStorage.m in Sources */, + 00EAC16C1B62E089003B7CEE /* RCTDevLoadingView.m in Sources */, 832348161A77A5AA00B55238 /* Layout.c in Sources */, 14F4D38B1AE1B7E40049C042 /* RCTProfile.m in Sources */, 13513F3C1B1F43F400FCE529 /* RCTProgressViewManager.m in Sources */,