[WIP] Added loadingView property to RCTRootView

This commit is contained in:
Nick Lockwood 2015-05-28 13:16:06 -07:00
parent 7e0064f097
commit 03889780b9
3 changed files with 108 additions and 19 deletions

View File

@ -11,6 +11,18 @@
#import "RCTBridge.h"
/**
* This notification is sent when the first subviews are added to the root view
* after the application has loaded. This is used to hide the `loadingView`, and
* is a good indicator that the application is ready to use.
*/
extern NSString *const RCTContentDidAppearNotification;
/**
* Native view used to host React-managed views within the app. Can be used just
* like any ordinary UIView. You can have multiple RCTRootViews on screen at
* once, all controlled by the same JavaScript application.
*/
@interface RCTRootView : UIView
/**
@ -67,4 +79,18 @@
*/
@property (nonatomic, strong, readonly) UIView *contentView;
/**
* A view to display while the JavaScript is loading, so users aren't presented
* with a blank screen. By default this is nil, but you can override it with
* (for example) a UIActivityIndicatorView or a placeholder image.
*/
@property (nonatomic, strong) UIView *loadingView;
/**
* Timings for hiding the loading view after the content has loaded. Both of
* these values default to 0.25 seconds.
*/
@property (nonatomic, assign) NSTimeInterval loadingViewFadeDelay;
@property (nonatomic, assign) NSTimeInterval loadingViewFadeDuration;
@end

View File

@ -25,6 +25,8 @@
#import "RCTWebViewExecutor.h"
#import "UIView+React.h"
NSString *const RCTContentDidAppearNotification = @"RCTContentDidAppearNotification";
@interface RCTBridge (RCTRootView)
@property (nonatomic, weak, readonly) RCTBridge *batchedBridge;
@ -39,6 +41,8 @@
@interface RCTRootContentView : RCTView <RCTInvalidating>
@property (nonatomic, readonly) BOOL contentHasAppeared;
- (instancetype)initWithFrame:(CGRect)frame bridge:(RCTBridge *)bridge;
@end
@ -64,14 +68,23 @@
_bridge = bridge;
_moduleName = moduleName;
_loadingViewFadeDelay = 0.25;
_loadingViewFadeDuration = 0.25;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(javaScriptDidLoad:)
name:RCTJavaScriptDidLoadNotification
object:_bridge];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(hideLoadingView)
name:RCTContentDidAppearNotification
object:self];
if (!_bridge.batchedBridge.isLoading) {
[self bundleFinishedLoading:_bridge.batchedBridge];
}
[self showLoadingView];
}
return self;
}
@ -106,6 +119,41 @@
RCT_IMPORT_METHOD(AppRegistry, runApplication)
RCT_IMPORT_METHOD(ReactNative, unmountComponentAtNodeAndRemoveContainer)
- (void)setLoadingView:(UIView *)loadingView
{
_loadingView = loadingView;
if (!_contentView.contentHasAppeared) {
[self showLoadingView];
}
}
- (void)showLoadingView
{
if (_loadingView && !_contentView.contentHasAppeared) {
_loadingView.hidden = NO;
[self addSubview:_loadingView];
}
}
- (void)hideLoadingView
{
if (_loadingView.superview == self && _contentView.contentHasAppeared) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_loadingViewFadeDelay * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
[UIView transitionWithView:self
duration:_loadingViewFadeDuration
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{
_loadingView.hidden = YES;
} completion:^(BOOL finished) {
[_loadingView removeFromSuperview];
}];
});
}
}
- (void)javaScriptDidLoad:(NSNotification *)notification
{
RCTBridge *bridge = notification.userInfo[@"bridge"];
@ -119,24 +167,18 @@ RCT_IMPORT_METHOD(ReactNative, unmountComponentAtNodeAndRemoveContainer)
return;
}
/**
* 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 removeFromSuperview];
_contentView = [[RCTRootContentView alloc] initWithFrame:self.bounds
bridge:bridge];
_contentView.backgroundColor = self.backgroundColor;
[self addSubview:_contentView];
[self insertSubview:_contentView atIndex:0];
NSString *moduleName = _moduleName ?: @"";
NSDictionary *appParameters = @{
@"rootTag": _contentView.reactTag,
@"initialProps": _initialProperties ?: @{},
};
[bridge enqueueJSCall:@"AppRegistry.runApplication"
args:@[moduleName, appParameters]];
});
@ -145,9 +187,11 @@ RCT_IMPORT_METHOD(ReactNative, unmountComponentAtNodeAndRemoveContainer)
- (void)layoutSubviews
{
[super layoutSubviews];
if (_contentView) {
_contentView.frame = self.bounds;
}
_loadingView.center = (CGPoint){
CGRectGetMidX(self.bounds),
CGRectGetMidY(self.bounds)
};
}
- (NSNumber *)reactTag
@ -155,6 +199,13 @@ RCT_IMPORT_METHOD(ReactNative, unmountComponentAtNodeAndRemoveContainer)
return _contentView.reactTag;
}
- (void)contentViewInvalidated
{
[_contentView removeFromSuperview];
_contentView = nil;
[self showLoadingView];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
@ -193,6 +244,18 @@ RCT_IMPORT_METHOD(ReactNative, unmountComponentAtNodeAndRemoveContainer)
return self;
}
- (void)insertReactSubview:(id<RCTViewNodeProtocol>)subview atIndex:(NSInteger)atIndex
{
[super insertReactSubview:subview atIndex:atIndex];
dispatch_async(dispatch_get_main_queue(), ^{
if (!_contentHasAppeared) {
_contentHasAppeared = YES;
[[NSNotificationCenter defaultCenter] postNotificationName:RCTContentDidAppearNotification
object:self.superview];
}
});
}
- (void)setFrame:(CGRect)frame
{
super.frame = frame;
@ -237,7 +300,7 @@ RCT_IMPORT_METHOD(ReactNative, unmountComponentAtNodeAndRemoveContainer)
{
if (self.isValid) {
self.userInteractionEnabled = NO;
[self removeFromSuperview];
[(RCTRootView *)self.superview contentViewInvalidated];
[_bridge enqueueJSCall:@"ReactNative.unmountComponentAtNodeAndRemoveContainer"
args:@[self.reactTag]];
}