mirror of
https://github.com/status-im/react-native.git
synced 2025-01-10 17:45:59 +00:00
d033c45f93
Summary:It was hard to understand which parts of the shadowview API are designed to be called only on the root view, and which were applicable to any view. This diff extracts rootview-specific logic out into a new RCTRootShadowView class. Reviewed By: majak Differential Revision: D3063905 fb-gh-sync-id: ef890cddfd7625fbd4bf5454314b441acdb03ac8 shipit-source-id: ef890cddfd7625fbd4bf5454314b441acdb03ac8
382 lines
11 KiB
Objective-C
382 lines
11 KiB
Objective-C
/**
|
|
* 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 "RCTRootView.h"
|
|
#import "RCTRootViewDelegate.h"
|
|
#import "RCTRootViewInternal.h"
|
|
|
|
#import <objc/runtime.h>
|
|
|
|
#import "RCTAssert.h"
|
|
#import "RCTBridge+Private.h"
|
|
#import "RCTEventDispatcher.h"
|
|
#import "RCTKeyCommands.h"
|
|
#import "RCTLog.h"
|
|
#import "RCTPerformanceLogger.h"
|
|
#import "RCTSourceCode.h"
|
|
#import "RCTTouchHandler.h"
|
|
#import "RCTUIManager.h"
|
|
#import "RCTUtils.h"
|
|
#import "RCTView.h"
|
|
#import "UIView+React.h"
|
|
#import "RCTProfile.h"
|
|
|
|
NSString *const RCTContentDidAppearNotification = @"RCTContentDidAppearNotification";
|
|
|
|
@interface RCTUIManager (RCTRootView)
|
|
|
|
- (NSNumber *)allocateRootTag;
|
|
|
|
@end
|
|
|
|
@interface RCTRootContentView : RCTView <RCTInvalidating>
|
|
|
|
@property (nonatomic, readonly) BOOL contentHasAppeared;
|
|
@property (nonatomic, readonly, strong) RCTTouchHandler *touchHandler;
|
|
|
|
- (instancetype)initWithFrame:(CGRect)frame
|
|
bridge:(RCTBridge *)bridge
|
|
reactTag:(NSNumber *)reactTag NS_DESIGNATED_INITIALIZER;
|
|
|
|
@end
|
|
|
|
@implementation RCTRootView
|
|
{
|
|
RCTBridge *_bridge;
|
|
NSString *_moduleName;
|
|
NSDictionary *_launchOptions;
|
|
RCTRootContentView *_contentView;
|
|
}
|
|
|
|
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
|
moduleName:(NSString *)moduleName
|
|
initialProperties:(NSDictionary *)initialProperties
|
|
{
|
|
RCTAssertMainThread();
|
|
RCTAssert(bridge, @"A bridge instance is required to create an RCTRootView");
|
|
RCTAssert(moduleName, @"A moduleName is required to create an RCTRootView");
|
|
|
|
RCT_PROFILE_BEGIN_EVENT(0, @"-[RCTRootView init]", nil);
|
|
|
|
if ((self = [super initWithFrame:CGRectZero])) {
|
|
|
|
self.backgroundColor = [UIColor whiteColor];
|
|
|
|
_bridge = bridge;
|
|
_moduleName = moduleName;
|
|
_appProperties = [initialProperties copy];
|
|
_loadingViewFadeDelay = 0.25;
|
|
_loadingViewFadeDuration = 0.25;
|
|
_sizeFlexibility = RCTRootViewSizeFlexibilityNone;
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(bridgeDidReload)
|
|
name:RCTJavaScriptWillStartLoadingNotification
|
|
object:_bridge];
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(javaScriptDidLoad:)
|
|
name:RCTJavaScriptDidLoadNotification
|
|
object:_bridge];
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(hideLoadingView)
|
|
name:RCTContentDidAppearNotification
|
|
object:self];
|
|
|
|
if (!_bridge.loading) {
|
|
[self bundleFinishedLoading:_bridge.batchedBridge];
|
|
}
|
|
|
|
[self showLoadingView];
|
|
}
|
|
|
|
RCT_PROFILE_END_EVENT(0, @"", nil);
|
|
|
|
return self;
|
|
}
|
|
|
|
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
|
|
moduleName:(NSString *)moduleName
|
|
initialProperties:(NSDictionary *)initialProperties
|
|
launchOptions:(NSDictionary *)launchOptions
|
|
{
|
|
RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:bundleURL
|
|
moduleProvider:nil
|
|
launchOptions:launchOptions];
|
|
|
|
return [self initWithBridge:bridge moduleName:moduleName initialProperties:initialProperties];
|
|
}
|
|
|
|
RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
|
|
RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
|
|
|
- (void)setBackgroundColor:(UIColor *)backgroundColor
|
|
{
|
|
super.backgroundColor = backgroundColor;
|
|
_contentView.backgroundColor = backgroundColor;
|
|
}
|
|
|
|
- (UIViewController *)reactViewController
|
|
{
|
|
return _reactViewController ?: [super reactViewController];
|
|
}
|
|
|
|
- (BOOL)canBecomeFirstResponder
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
- (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) {
|
|
if (_loadingViewFadeDuration > 0) {
|
|
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:^(__unused BOOL finished) {
|
|
[_loadingView removeFromSuperview];
|
|
}];
|
|
});
|
|
} else {
|
|
_loadingView.hidden = YES;
|
|
[_loadingView removeFromSuperview];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (NSNumber *)reactTag
|
|
{
|
|
RCTAssertMainThread();
|
|
if (!super.reactTag) {
|
|
/**
|
|
* 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 the
|
|
* react tag must be re-assigned every time a new UIManager is created.
|
|
*/
|
|
self.reactTag = [_bridge.uiManager allocateRootTag];
|
|
}
|
|
return super.reactTag;
|
|
}
|
|
|
|
- (void)bridgeDidReload
|
|
{
|
|
RCTAssertMainThread();
|
|
// Clear the reactTag so it can be re-assigned
|
|
self.reactTag = nil;
|
|
}
|
|
|
|
- (void)javaScriptDidLoad:(NSNotification *)notification
|
|
{
|
|
RCTAssertMainThread();
|
|
RCTBridge *bridge = notification.userInfo[@"bridge"];
|
|
[self bundleFinishedLoading:bridge];
|
|
}
|
|
|
|
- (void)bundleFinishedLoading:(RCTBridge *)bridge
|
|
{
|
|
if (!bridge.valid) {
|
|
return;
|
|
}
|
|
|
|
[_contentView removeFromSuperview];
|
|
_contentView = [[RCTRootContentView alloc] initWithFrame:self.bounds
|
|
bridge:bridge
|
|
reactTag:self.reactTag];
|
|
[self runApplication:bridge];
|
|
|
|
_contentView.backgroundColor = self.backgroundColor;
|
|
[self insertSubview:_contentView atIndex:0];
|
|
}
|
|
|
|
- (void)runApplication:(RCTBridge *)bridge
|
|
{
|
|
NSString *moduleName = _moduleName ?: @"";
|
|
NSDictionary *appParameters = @{
|
|
@"rootTag": _contentView.reactTag,
|
|
@"initialProps": _appProperties ?: @{},
|
|
};
|
|
|
|
[bridge enqueueJSCall:@"AppRegistry.runApplication"
|
|
args:@[moduleName, appParameters]];
|
|
}
|
|
|
|
- (void)setSizeFlexibility:(RCTRootViewSizeFlexibility)sizeFlexibility
|
|
{
|
|
_sizeFlexibility = sizeFlexibility;
|
|
[self setNeedsLayout];
|
|
}
|
|
|
|
- (void)layoutSubviews
|
|
{
|
|
[super layoutSubviews];
|
|
_contentView.frame = self.bounds;
|
|
_loadingView.center = (CGPoint){
|
|
CGRectGetMidX(self.bounds),
|
|
CGRectGetMidY(self.bounds)
|
|
};
|
|
}
|
|
|
|
- (void)setAppProperties:(NSDictionary *)appProperties
|
|
{
|
|
RCTAssertMainThread();
|
|
|
|
if ([_appProperties isEqualToDictionary:appProperties]) {
|
|
return;
|
|
}
|
|
|
|
_appProperties = [appProperties copy];
|
|
|
|
if (_contentView && _bridge.valid && !_bridge.loading) {
|
|
[self runApplication:_bridge.batchedBridge];
|
|
}
|
|
}
|
|
|
|
- (void)setIntrinsicSize:(CGSize)intrinsicSize
|
|
{
|
|
BOOL oldSizeHasAZeroDimension = _intrinsicSize.height == 0 || _intrinsicSize.width == 0;
|
|
BOOL newSizeHasAZeroDimension = intrinsicSize.height == 0 || intrinsicSize.width == 0;
|
|
BOOL bothSizesHaveAZeroDimension = oldSizeHasAZeroDimension && newSizeHasAZeroDimension;
|
|
|
|
BOOL sizesAreEqual = CGSizeEqualToSize(_intrinsicSize, intrinsicSize);
|
|
|
|
_intrinsicSize = intrinsicSize;
|
|
|
|
// Don't notify the delegate if the content remains invisible or its size has not changed
|
|
if (bothSizesHaveAZeroDimension || sizesAreEqual) {
|
|
return;
|
|
}
|
|
|
|
[_delegate rootViewDidChangeIntrinsicSize:self];
|
|
}
|
|
|
|
- (void)contentViewInvalidated
|
|
{
|
|
[_contentView removeFromSuperview];
|
|
_contentView = nil;
|
|
[self showLoadingView];
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
[_contentView invalidate];
|
|
}
|
|
|
|
- (void)cancelTouches
|
|
{
|
|
[[_contentView touchHandler] cancel];
|
|
}
|
|
|
|
@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
|
|
|
|
@implementation RCTRootContentView
|
|
{
|
|
__weak RCTBridge *_bridge;
|
|
UIColor *_backgroundColor;
|
|
}
|
|
|
|
- (instancetype)initWithFrame:(CGRect)frame
|
|
bridge:(RCTBridge *)bridge
|
|
reactTag:(NSNumber *)reactTag
|
|
{
|
|
if ((self = [super initWithFrame:frame])) {
|
|
_bridge = bridge;
|
|
self.reactTag = reactTag;
|
|
_touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge];
|
|
[self addGestureRecognizer:_touchHandler];
|
|
[_bridge.uiManager registerRootView:self];
|
|
self.layer.backgroundColor = NULL;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
RCT_NOT_IMPLEMENTED(-(instancetype)initWithFrame:(CGRect)frame)
|
|
RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder:(nonnull NSCoder *)aDecoder)
|
|
|
|
- (void)insertReactSubview:(id<RCTComponent>)subview atIndex:(NSInteger)atIndex
|
|
{
|
|
[super insertReactSubview:subview atIndex:atIndex];
|
|
RCTPerformanceLoggerEnd(RCTPLTTI);
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
if (!_contentHasAppeared) {
|
|
_contentHasAppeared = YES;
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:RCTContentDidAppearNotification
|
|
object:self.superview];
|
|
}
|
|
});
|
|
}
|
|
|
|
- (void)setFrame:(CGRect)frame
|
|
{
|
|
super.frame = frame;
|
|
if (self.reactTag && _bridge.isValid) {
|
|
[_bridge.uiManager setFrame:frame forView:self];
|
|
}
|
|
}
|
|
|
|
- (void)setBackgroundColor:(UIColor *)backgroundColor
|
|
{
|
|
_backgroundColor = backgroundColor;
|
|
if (self.reactTag && _bridge.isValid) {
|
|
[_bridge.uiManager setBackgroundColor:backgroundColor forView:self];
|
|
}
|
|
}
|
|
|
|
- (UIColor *)backgroundColor
|
|
{
|
|
return _backgroundColor;
|
|
}
|
|
|
|
- (void)invalidate
|
|
{
|
|
if (self.userInteractionEnabled) {
|
|
self.userInteractionEnabled = NO;
|
|
[(RCTRootView *)self.superview contentViewInvalidated];
|
|
[_bridge enqueueJSCall:@"AppRegistry.unmountApplicationComponentAtRootTag"
|
|
args:@[self.reactTag]];
|
|
}
|
|
}
|
|
|
|
@end
|