mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
2160377574
Summary: Refs: [0.62 release](https://reactnative.dev/blog/#moving-apple-tv-to-react-native-tvos), https://github.com/facebook/react-native/issues/28706, https://github.com/facebook/react-native/issues/28743, https://github.com/facebook/react-native/issues/29018 This PR removes most of the tvOS remnants in the code. Most of the changes are related to the tvOS platform removal from `.podspec` files, tvOS specific conditionals removal (Obj-C + JS) or tvOS CI/testing pipeline related code. In addition to the changes listed above I have removed the deprecated `Platform.isTVOS` method. I'm not sure how `Platform.isTV` method is correlated with Android TV devices support which is technically not deprecated in the core so I left this method untouched for now. ## Changelog <!-- Help reviewers and the release process by writing your own changelog entry. For an example, see: https://github.com/facebook/react-native/wiki/Changelog --> * **[Internal] [Removed]** - remove most of tvOS remnants from the code: * `TVEventHandler`, `TVTouchable`, `RCTTVView`, `RCTTVRemoteHandler` and `RCTTVNavigationEventEmitter` * **[Internal] [Removed]** - remove `TARGET_TV_OS` flag and all the usages * **[iOS] [Removed]** - remove deprecated `Platform.isTVOS` method * **[iOS] [Removed]** - remove deprecated and TV related props from View: * `isTVSelectable`, `hasTVPreferredFocus` and `tvParallaxProperties` * **[iOS] [Removed]** - remove `BackHandler` utility implementation Pull Request resolved: https://github.com/facebook/react-native/pull/29407 Test Plan: Local tests (and iOS CI run) do not yield any errors, but I'm not sure how the CI pipeline would react to those changes. That is the reason why this PR is being posted as Draft. Some tweaks and code adjustment could be required. Reviewed By: PeteTheHeat Differential Revision: D22619441 Pulled By: shergin fbshipit-source-id: 9aaf3840c5e8bd469c2cfcfa7c5b441ef71b30b6
390 lines
11 KiB
Objective-C
390 lines
11 KiB
Objective-C
/*
|
|
* 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"
|
|
|
|
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];
|
|
|
|
[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)
|
|
|
|
#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
|