123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411 |
- /*
- * Copyright (c) Facebook, Inc. and its affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- */
- #import "RCTRootView.h"
- #import "RCTRootViewDelegate.h"
- #import "RCTRootViewInternal.h"
- #import <objc/runtime.h>
- #import "RCTAssert.h"
- #import "RCTBridge+Private.h"
- #import "RCTBridge.h"
- #import "RCTConstants.h"
- #import "RCTEventDispatcher.h"
- #import "RCTKeyCommands.h"
- #import "RCTLog.h"
- #import "RCTPerformanceLogger.h"
- #import "RCTProfile.h"
- #import "RCTRootContentView.h"
- #import "RCTRootShadowView.h"
- #import "RCTTouchHandler.h"
- #import "RCTUIManager.h"
- #import "RCTUIManagerUtils.h"
- #import "RCTUtils.h"
- #import "RCTView.h"
- #import "UIView+React.h"
- #if TARGET_OS_TV
- #import "RCTTVNavigationEventEmitter.h"
- #import "RCTTVRemoteHandler.h"
- #endif
- NSString *const RCTContentDidAppearNotification = @"RCTContentDidAppearNotification";
- @interface RCTUIManager (RCTRootView)
- - (NSNumber *)allocateRootTag;
- @end
- @implementation RCTRootView {
- RCTBridge *_bridge;
- NSString *_moduleName;
- RCTRootContentView *_contentView;
- BOOL _passThroughTouches;
- CGSize _intrinsicContentSize;
- }
- - (instancetype)initWithBridge:(RCTBridge *)bridge
- moduleName:(NSString *)moduleName
- initialProperties:(NSDictionary *)initialProperties
- {
- RCTAssertMainQueue();
- 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(RCTProfileTagAlways, @"-[RCTRootView init]", nil);
- if (!bridge.isLoading) {
- [bridge.performanceLogger markStartForTag:RCTPLTTI];
- }
- if (self = [super initWithFrame:CGRectZero]) {
- self.backgroundColor = [UIColor whiteColor];
- _bridge = bridge;
- _moduleName = moduleName;
- _appProperties = [initialProperties copy];
- _loadingViewFadeDelay = 0.25;
- _loadingViewFadeDuration = 0.25;
- _sizeFlexibility = RCTRootViewSizeFlexibilityNone;
- _minimumSize = CGSizeZero;
- [[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 TARGET_OS_TV
- self.tvRemoteHandler = [RCTTVRemoteHandler new];
- for (NSString *key in [self.tvRemoteHandler.tvRemoteGestureRecognizers allKeys]) {
- [self addGestureRecognizer:self.tvRemoteHandler.tvRemoteGestureRecognizers[key]];
- }
- #endif
- [self showLoadingView];
- // Immediately schedule the application to be started.
- // (Sometimes actual `_bridge` is already batched bridge here.)
- [self bundleFinishedLoading:([_bridge batchedBridge] ?: _bridge)];
- }
- RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
- 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)
- #if TARGET_OS_TV
- - (UIView *)preferredFocusedView
- {
- if (self.reactPreferredFocusedView) {
- return self.reactPreferredFocusedView;
- }
- return [super preferredFocusedView];
- }
- #endif
- #pragma mark - passThroughTouches
- - (BOOL)passThroughTouches
- {
- return _contentView.passThroughTouches;
- }
- - (void)setPassThroughTouches:(BOOL)passThroughTouches
- {
- _passThroughTouches = passThroughTouches;
- _contentView.passThroughTouches = passThroughTouches;
- }
- #pragma mark - Layout
- - (CGSize)sizeThatFits:(CGSize)size
- {
- CGSize fitSize = _intrinsicContentSize;
- CGSize currentSize = self.bounds.size;
- // Following the current `size` and current `sizeFlexibility` policy.
- fitSize = CGSizeMake(
- _sizeFlexibility & RCTRootViewSizeFlexibilityWidth ? fitSize.width : currentSize.width,
- _sizeFlexibility & RCTRootViewSizeFlexibilityHeight ? fitSize.height : currentSize.height);
- // Following the given size constraints.
- fitSize = CGSizeMake(MIN(size.width, fitSize.width), MIN(size.height, fitSize.height));
- return fitSize;
- }
- - (void)layoutSubviews
- {
- [super layoutSubviews];
- _contentView.frame = self.bounds;
- _loadingView.center = (CGPoint){CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds)};
- }
- - (void)setMinimumSize:(CGSize)minimumSize
- {
- if (CGSizeEqualToSize(_minimumSize, minimumSize)) {
- return;
- }
- _minimumSize = minimumSize;
- __block NSNumber *tag = self.reactTag;
- __weak typeof(self) weakSelf = self;
- RCTExecuteOnUIManagerQueue(^{
- __strong typeof(self) strongSelf = weakSelf;
- if (strongSelf && strongSelf->_bridge.isValid) {
- RCTRootShadowView *shadowView = (RCTRootShadowView *)[strongSelf->_bridge.uiManager shadowViewForReactTag:tag];
- shadowView.minimumSize = minimumSize;
- }
- });
- }
- - (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:self->_loadingViewFadeDuration
- options:UIViewAnimationOptionTransitionCrossDissolve
- animations:^{
- self->_loadingView.hidden = YES;
- }
- completion:^(__unused BOOL finished) {
- [self->_loadingView removeFromSuperview];
- }];
- });
- } else {
- _loadingView.hidden = YES;
- [_loadingView removeFromSuperview];
- }
- }
- }
- - (NSNumber *)reactTag
- {
- RCTAssertMainQueue();
- 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 = RCTAllocateRootViewTag();
- }
- return super.reactTag;
- }
- - (void)bridgeDidReload
- {
- RCTAssertMainQueue();
- // Clear the reactTag so it can be re-assigned
- self.reactTag = nil;
- }
- - (void)javaScriptDidLoad:(NSNotification *)notification
- {
- RCTAssertMainQueue();
- // Use the (batched) bridge that's sent in the notification payload, so the
- // RCTRootContentView is scoped to the right bridge
- RCTBridge *bridge = notification.userInfo[@"bridge"];
- if (bridge != _contentView.bridge) {
- [self bundleFinishedLoading:bridge];
- }
- }
- - (void)bundleFinishedLoading:(RCTBridge *)bridge
- {
- RCTAssert(bridge != nil, @"Bridge cannot be nil");
- if (!bridge.valid) {
- return;
- }
- [_contentView removeFromSuperview];
- _contentView = [[RCTRootContentView alloc] initWithFrame:self.bounds
- bridge:bridge
- reactTag:self.reactTag
- sizeFlexiblity:_sizeFlexibility];
- [self runApplication:bridge];
- _contentView.passThroughTouches = _passThroughTouches;
- [self insertSubview:_contentView atIndex:0];
- if (_sizeFlexibility == RCTRootViewSizeFlexibilityNone) {
- self.intrinsicContentSize = self.bounds.size;
- }
- }
- - (void)runApplication:(RCTBridge *)bridge
- {
- NSString *moduleName = _moduleName ?: @"";
- NSDictionary *appParameters = @{
- @"rootTag" : _contentView.reactTag,
- @"initialProps" : _appProperties ?: @{},
- };
- RCTLogInfo(@"Running application %@ (%@)", moduleName, appParameters);
- [bridge enqueueJSCall:@"AppRegistry" method:@"runApplication" args:@[ moduleName, appParameters ] completion:NULL];
- }
- - (void)setSizeFlexibility:(RCTRootViewSizeFlexibility)sizeFlexibility
- {
- if (_sizeFlexibility == sizeFlexibility) {
- return;
- }
- _sizeFlexibility = sizeFlexibility;
- [self setNeedsLayout];
- _contentView.sizeFlexibility = _sizeFlexibility;
- }
- - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
- {
- // The root view itself should never receive touches
- UIView *hitView = [super hitTest:point withEvent:event];
- if (self.passThroughTouches && hitView == self) {
- return nil;
- }
- return hitView;
- }
- - (void)setAppProperties:(NSDictionary *)appProperties
- {
- RCTAssertMainQueue();
- if ([_appProperties isEqualToDictionary:appProperties]) {
- return;
- }
- _appProperties = [appProperties copy];
- if (_contentView && _bridge.valid && !_bridge.loading) {
- [self runApplication:_bridge];
- }
- }
- - (void)setIntrinsicContentSize:(CGSize)intrinsicContentSize
- {
- BOOL oldSizeHasAZeroDimension = _intrinsicContentSize.height == 0 || _intrinsicContentSize.width == 0;
- BOOL newSizeHasAZeroDimension = intrinsicContentSize.height == 0 || intrinsicContentSize.width == 0;
- BOOL bothSizesHaveAZeroDimension = oldSizeHasAZeroDimension && newSizeHasAZeroDimension;
- BOOL sizesAreEqual = CGSizeEqualToSize(_intrinsicContentSize, intrinsicContentSize);
- _intrinsicContentSize = intrinsicContentSize;
- // Don't notify the delegate if the content remains invisible or its size has not changed
- if (bothSizesHaveAZeroDimension || sizesAreEqual) {
- return;
- }
- [self invalidateIntrinsicContentSize];
- [self.superview setNeedsLayout];
- [_delegate rootViewDidChangeIntrinsicSize:self];
- }
- - (CGSize)intrinsicContentSize
- {
- return _intrinsicContentSize;
- }
- - (void)contentViewInvalidated
- {
- [_contentView removeFromSuperview];
- _contentView = nil;
- [self showLoadingView];
- }
- - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
- {
- [super traitCollectionDidChange:previousTraitCollection];
- [[NSNotificationCenter defaultCenter]
- postNotificationName:RCTUserInterfaceStyleDidChangeNotification
- object:self
- userInfo:@{
- RCTUserInterfaceStyleDidChangeNotificationTraitCollectionKey : self.traitCollection,
- }];
- }
- - (void)dealloc
- {
- [_contentView invalidate];
- }
- @end
- @implementation RCTRootView (Deprecated)
- - (CGSize)intrinsicSize
- {
- RCTLogWarn(@"Calling deprecated `[-RCTRootView intrinsicSize]`.");
- return self.intrinsicContentSize;
- }
- - (void)cancelTouches
- {
- RCTLogWarn(@"`-[RCTRootView cancelTouches]` is deprecated and will be deleted soon.");
- [[_contentView touchHandler] cancel];
- }
- @end
|