From 68e9b0b949065b74d437de77916e2a6dad71e6f8 Mon Sep 17 00:00:00 2001 From: Devan Buggay Date: Tue, 13 Jan 2026 13:47:08 -0800 Subject: [PATCH 1/7] Compile out UIView mulitpleTouchEnabled for AppleTV (#55127) Summary: `multipleTouchEnabled` is not available for UIViews for AppleTV, compiling them out. Changelog: [internal] Reviewed By: Abbondanzo Differential Revision: D90513833 --- .../Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm | 2 ++ packages/react-native/React/Views/RCTComponentData.mm | 2 ++ 2 files changed, 4 insertions(+) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm index 9895b22dbfb288..a3947e808a3d8c 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -67,7 +67,9 @@ - (instancetype)initWithFrame:(CGRect)frame if (self = [super initWithFrame:frame]) { _props = ViewShadowNode::defaultSharedProps(); _reactSubviews = [NSMutableArray new]; +#if !TARGET_OS_TV self.multipleTouchEnabled = YES; +#endif _useCustomContainerView = NO; _removeClippedSubviews = NO; } diff --git a/packages/react-native/React/Views/RCTComponentData.mm b/packages/react-native/React/Views/RCTComponentData.mm index 36de415c2b4f44..64190118a881ea 100644 --- a/packages/react-native/React/Views/RCTComponentData.mm +++ b/packages/react-native/React/Views/RCTComponentData.mm @@ -88,7 +88,9 @@ - (UIView *)createViewWithTag:(nullable NSNumber *)tag rootTag:(nullable NSNumbe UIView *view = [self.manager view]; view.reactTag = tag; view.rootTag = rootTag; +#if !TARGET_OS_TV view.multipleTouchEnabled = YES; +#endif view.userInteractionEnabled = YES; // required for touch handling view.layer.allowsGroupOpacity = YES; // required for touch handling return view; From 81865626a2215b282a98b6558ebd02a48e72734a Mon Sep 17 00:00:00 2001 From: Devan Buggay Date: Tue, 13 Jan 2026 13:47:08 -0800 Subject: [PATCH 2/7] Compile out orientation usage for AppleTV (#55128) Summary: UIOrientation usage is unsupported on AppleTV, compiling out. Changelog: [internal] Reviewed By: Abbondanzo Differential Revision: D90513975 --- packages/react-native/React/Base/RCTUtils.mm | 9 +++++++++ .../React/CoreModules/RCTDeviceInfo.mm | 7 +++++-- .../Modal/RCTModalHostViewComponentView.mm | 14 ++++++++++++++ .../react-native/React/Modules/RCTUIManager.mm | 2 +- 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/packages/react-native/React/Base/RCTUtils.mm b/packages/react-native/React/Base/RCTUtils.mm index e6f8aa1062bba2..8744d6fb798e1b 100644 --- a/packages/react-native/React/Base/RCTUtils.mm +++ b/packages/react-native/React/Base/RCTUtils.mm @@ -38,7 +38,9 @@ BOOL RCTIsHomeAssetURL(NSURL *__nullable imageURL); // Returns the current device's orientation +#if !TARGET_OS_TV UIDeviceOrientation RCTDeviceOrientation(void); +#endif // Whether the New Architecture is enabled or not BOOL RCTIsNewArchEnabled(void) @@ -381,10 +383,12 @@ CGFloat RCTFontSizeMultiplier(void) return mapping[RCTSharedApplication().preferredContentSizeCategory].floatValue; } +#if !TARGET_OS_TV UIDeviceOrientation RCTDeviceOrientation(void) { return [[UIDevice currentDevice] orientation]; } +#endif CGSize RCTScreenSize(void) { @@ -397,11 +401,16 @@ CGSize RCTScreenSize(void) }); }); +#if !TARGET_OS_TV if (UIDeviceOrientationIsLandscape(RCTDeviceOrientation())) { return CGSizeMake(portraitSize.height, portraitSize.width); } else { return CGSizeMake(portraitSize.width, portraitSize.height); } +#else + // tvOS doesn't have device orientation, always return landscape size + return CGSizeMake(portraitSize.height, portraitSize.width); +#endif } CGSize RCTViewportSize(void) diff --git a/packages/react-native/React/CoreModules/RCTDeviceInfo.mm b/packages/react-native/React/CoreModules/RCTDeviceInfo.mm index 8620af60246d48..c5946382d08f7c 100644 --- a/packages/react-native/React/CoreModules/RCTDeviceInfo.mm +++ b/packages/react-native/React/CoreModules/RCTDeviceInfo.mm @@ -25,7 +25,9 @@ @interface RCTDeviceInfo () _invalidated; @@ -103,10 +105,11 @@ - (void)initialize name:UIApplicationDidBecomeActiveNotification object:nil]; -#if TARGET_OS_IOS - +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST _currentInterfaceOrientation = RCTKeyWindow().windowScene.interfaceOrientation; +#endif +#if TARGET_OS_IOS [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(interfaceFrameDidChange) name:UIDeviceOrientationDidChangeNotification diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/Modal/RCTModalHostViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/Modal/RCTModalHostViewComponentView.mm index bbba4cd45ee60e..c27f816e14e1bd 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/Modal/RCTModalHostViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/Modal/RCTModalHostViewComponentView.mm @@ -21,6 +21,7 @@ using namespace facebook::react; +#if !TARGET_OS_TV static UIInterfaceOrientationMask supportedOrientationsMask(ModalHostViewSupportedOrientationsMask mask) { UIInterfaceOrientationMask supportedOrientations = 0; @@ -55,6 +56,7 @@ static UIInterfaceOrientationMask supportedOrientationsMask(ModalHostViewSupport return supportedOrientations; } +#endif static std::tuple animationConfiguration(const ModalHostViewAnimationType animation) { @@ -77,9 +79,21 @@ static UIModalPresentationStyle presentationConfiguration(const ModalHostViewPro case ModalHostViewPresentationStyle::FullScreen: return UIModalPresentationFullScreen; case ModalHostViewPresentationStyle::PageSheet: +#if !TARGET_OS_TV return UIModalPresentationPageSheet; +#else + return UIModalPresentationFullScreen; +#endif case ModalHostViewPresentationStyle::FormSheet: +#if TARGET_OS_TV + if (@available(tvOS 26.0, *)) { + return UIModalPresentationFormSheet; + } else { + return UIModalPresentationFullScreen; + } +#else return UIModalPresentationFormSheet; +#endif case ModalHostViewPresentationStyle::OverFullScreen: return UIModalPresentationOverFullScreen; } diff --git a/packages/react-native/React/Modules/RCTUIManager.mm b/packages/react-native/React/Modules/RCTUIManager.mm index 689b63d077a39c..79d15605cafcf4 100644 --- a/packages/react-native/React/Modules/RCTUIManager.mm +++ b/packages/react-native/React/Modules/RCTUIManager.mm @@ -321,6 +321,7 @@ - (void)didReceiveNewContentSizeMultiplier }); } +#if TARGET_OS_IOS // Names and coordinate system from html5 spec: // https://developer.mozilla.org/en-US/docs/Web/API/Screen.orientation // https://developer.mozilla.org/en-US/docs/Web/API/Screen.lockOrientation @@ -360,7 +361,6 @@ - (void)didReceiveNewContentSizeMultiplier }; } -#if TARGET_OS_IOS - (void)namedOrientationDidChange { NSDictionary *orientationEvent = deviceOrientationEventBody([UIDevice currentDevice].orientation); From 40d0484673972f70d0d151957e1b687b3323218a Mon Sep 17 00:00:00 2001 From: Devan Buggay Date: Tue, 13 Jan 2026 13:47:08 -0800 Subject: [PATCH 3/7] Compile out various unsupported TextInput APIs for AppleTV (#55129) Summary: TextInput uses a lot of UIKit APIs that are unavailable on AppleTV, compiling them out. Changelog: [internal] Reviewed By: shwanton Differential Revision: D90514227 --- .../Text/TextInput/Multiline/RCTUITextView.mm | 12 ++++++-- .../RCTBackedTextInputDelegateAdapter.mm | 28 +++++++++++++++++-- .../TextInput/Singleline/RCTUITextField.mm | 4 +-- .../react-native/React/Base/RCTConvert.mm | 6 ++++ .../TextInput/RCTTextInputComponentView.mm | 12 ++++++-- .../TextInput/RCTTextInputUtils.h | 2 ++ .../TextInput/RCTTextInputUtils.mm | 4 ++- 7 files changed, 59 insertions(+), 9 deletions(-) diff --git a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.mm b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.mm index 6e9c3841cee196..cbda3771e97cfa 100644 --- a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.mm +++ b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.mm @@ -21,6 +21,7 @@ @implementation RCTUITextView { NSArray *_initialValueLeadingBarButtonGroups; NSArray *_initialValueTrailingBarButtonGroups; NSArray *_acceptDragAndDropTypes; + BOOL _disableKeyboardShortcuts; } static UIFont *defaultPlaceholderFont(void) @@ -53,7 +54,9 @@ - (instancetype)initWithFrame:(CGRect)frame self.textColor = [UIColor blackColor]; // This line actually removes 5pt (default value) left and right padding in UITextView. self.textContainer.lineFragmentPadding = 0; +#if !TARGET_OS_TV self.scrollsToTop = NO; +#endif self.scrollEnabled = YES; _initialValueLeadingBarButtonGroups = nil; _initialValueTrailingBarButtonGroups = nil; @@ -149,6 +152,7 @@ - (void)textDidChange - (void)setDisableKeyboardShortcuts:(BOOL)disableKeyboardShortcuts { + _disableKeyboardShortcuts = disableKeyboardShortcuts; #if TARGET_OS_IOS // Initialize the initial values only once if (_initialValueLeadingBarButtonGroups == nil) { @@ -165,10 +169,14 @@ - (void)setDisableKeyboardShortcuts:(BOOL)disableKeyboardShortcuts self.inputAssistantItem.leadingBarButtonGroups = _initialValueLeadingBarButtonGroups; self.inputAssistantItem.trailingBarButtonGroups = _initialValueTrailingBarButtonGroups; } - _disableKeyboardShortcuts = disableKeyboardShortcuts; #endif } +- (BOOL)disableKeyboardShortcuts +{ + return _disableKeyboardShortcuts; +} + #pragma mark - Overrides - (void)setFont:(UIFont *)font @@ -306,7 +314,7 @@ - (BOOL)canPerformAction:(SEL)action withSender:(id)sender - (void)buildMenuWithBuilder:(id)builder { -#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 170000 +#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 170000 && !TARGET_OS_TV if (@available(iOS 17.0, *)) { if (_contextMenuHidden) { [builder removeMenuForIdentifier:UIMenuAutoFill]; diff --git a/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm b/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm index 82d9a79af7345f..8db8703fb66501 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm +++ b/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm @@ -11,7 +11,13 @@ static void *TextFieldSelectionObservingContext = &TextFieldSelectionObservingContext; -@interface RCTBackedTextFieldDelegateAdapter () +@interface RCTBackedTextFieldDelegateAdapter () < + UITextFieldDelegate +#if !TARGET_OS_TV + , + UITextDropDelegate +#endif + > @end @implementation RCTBackedTextFieldDelegateAdapter { @@ -25,7 +31,9 @@ - (instancetype)initWithTextField:(UITextField * if (self = [super init]) { _backedTextInputView = backedTextInputView; backedTextInputView.delegate = self; +#if !TARGET_OS_TV backedTextInputView.textDropDelegate = self; +#endif [_backedTextInputView addTarget:self action:@selector(textFieldDidChange) @@ -160,6 +168,8 @@ - (void)textFieldProbablyDidChangeSelection [_backedTextInputView.textInputDelegate textInputDidChangeSelection]; } +#if !TARGET_OS_TV + #pragma mark - UITextDropDelegate - (UITextDropEditability)textDroppableView:(UIView *)textDroppableView @@ -196,11 +206,19 @@ - (bool)_shouldAcceptDrop:(id)drop } } +#endif + @end #pragma mark - RCTBackedTextViewDelegateAdapter (for UITextView) -@interface RCTBackedTextViewDelegateAdapter () +@interface RCTBackedTextViewDelegateAdapter () < + UITextViewDelegate +#if !TARGET_OS_TV + , + UITextDropDelegate +#endif + > @end @implementation RCTBackedTextViewDelegateAdapter { @@ -216,7 +234,9 @@ - (instancetype)initWithTextView:(UITextView *)b if (self = [super init]) { _backedTextInputView = backedTextInputView; backedTextInputView.delegate = self; +#if !TARGET_OS_TV backedTextInputView.textDropDelegate = self; +#endif } return self; @@ -342,6 +362,8 @@ - (void)textViewProbablyDidChangeSelection [_backedTextInputView.textInputDelegate textInputDidChangeSelection]; } +#if !TARGET_OS_TV + #pragma mark - UITextDropDelegate - (UITextDropEditability)textDroppableView:(UIView *)textDroppableView @@ -378,4 +400,6 @@ - (bool)_shouldAcceptDrop:(id)drop } } +#endif + @end diff --git a/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm b/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm index 377f41e41828ec..052c003476d18c 100644 --- a/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm +++ b/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm @@ -136,6 +136,7 @@ - (void)setSecureTextEntry:(BOOL)secureTextEntry - (void)setDisableKeyboardShortcuts:(BOOL)disableKeyboardShortcuts { + _disableKeyboardShortcuts = disableKeyboardShortcuts; #if TARGET_OS_IOS // Initialize the initial values only once if (_initialValueLeadingBarButtonGroups == nil) { @@ -152,7 +153,6 @@ - (void)setDisableKeyboardShortcuts:(BOOL)disableKeyboardShortcuts self.inputAssistantItem.leadingBarButtonGroups = _initialValueLeadingBarButtonGroups; self.inputAssistantItem.trailingBarButtonGroups = _initialValueTrailingBarButtonGroups; } - _disableKeyboardShortcuts = disableKeyboardShortcuts; #endif } @@ -186,7 +186,7 @@ - (BOOL)canPerformAction:(SEL)action withSender:(id)sender - (void)buildMenuWithBuilder:(id)builder { #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 170000 - if (@available(iOS 17.0, *)) { + if (@available(iOS 17.0, tvOS 17.0, *)) { if (_contextMenuHidden) { [builder removeMenuForIdentifier:UIMenuAutoFill]; } diff --git a/packages/react-native/React/Base/RCTConvert.mm b/packages/react-native/React/Base/RCTConvert.mm index 0194bfa6695073..0aacbd8561077f 100644 --- a/packages/react-native/React/Base/RCTConvert.mm +++ b/packages/react-native/React/Base/RCTConvert.mm @@ -445,6 +445,7 @@ + (UIKeyboardType)UIKeyboardType:(id)json RCT_DYNAMIC return type; } +#if !TARGET_OS_TV RCT_MULTI_ENUM_CONVERTER( UIDataDetectorTypes, (@{ @@ -460,6 +461,7 @@ + (UIKeyboardType)UIKeyboardType:(id)json RCT_DYNAMIC }), UIDataDetectorTypePhoneNumber, unsignedLongLongValue) +#endif RCT_ENUM_CONVERTER( UIKeyboardAppearance, @@ -517,8 +519,12 @@ + (UIKeyboardType)UIKeyboardType:(id)json RCT_DYNAMIC UIModalPresentationStyle, (@{ @"fullScreen" : @(UIModalPresentationFullScreen), +#if !TARGET_OS_TV @"pageSheet" : @(UIModalPresentationPageSheet), +#endif +#if !TARGET_OS_TV || __TV_OS_VERSION_MIN_REQUIRED >= 260000 @"formSheet" : @(UIModalPresentationFormSheet), +#endif @"overFullScreen" : @(UIModalPresentationOverFullScreen), }), UIModalPresentationFullScreen, diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index d02e003a439e41..cbbc402de42fea 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -32,8 +32,12 @@ @interface RCTTextInputComponentView () < RCTBackedTextInputDelegate, - RCTTextInputViewProtocol, - UIDropInteractionDelegate> + RCTTextInputViewProtocol +#if !TARGET_OS_TV + , + UIDropInteractionDelegate +#endif + > @end static NSSet *returnKeyTypesSet; @@ -211,11 +215,13 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & _backedTextInputView.editable = newTextInputProps.traits.editable; } +#if !TARGET_OS_TV if (newTextInputProps.multiline && newTextInputProps.traits.dataDetectorTypes != oldTextInputProps.traits.dataDetectorTypes) { _backedTextInputView.dataDetectorTypes = RCTUITextViewDataDetectorTypesFromStringVector(newTextInputProps.traits.dataDetectorTypes); } +#endif if (newTextInputProps.traits.enablesReturnKeyAutomatically != oldTextInputProps.traits.enablesReturnKeyAutomatically) { @@ -665,6 +671,7 @@ - (void)setDefaultInputAccessoryView _hasInputAccessoryView = shouldHaveInputAccessoryView; +#if !TARGET_OS_TV if (shouldHaveInputAccessoryView) { NSString *buttonLabel = inputAccessoryViewButtonLabel != nil ? inputAccessoryViewButtonLabel : [self returnKeyTypeToString:returnKeyType]; @@ -682,6 +689,7 @@ - (void)setDefaultInputAccessoryView } else { _backedTextInputView.inputAccessoryView = nil; } +#endif if (_backedTextInputView.isFirstResponder) { [_backedTextInputView reloadInputViews]; diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.h b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.h index 5f20d8762f4c58..90a2816bf2adc7 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.h +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.h @@ -41,6 +41,8 @@ UITextInputPasswordRules *RCTUITextInputPasswordRulesFromString(const std::strin UITextSmartInsertDeleteType RCTUITextSmartInsertDeleteTypeFromOptionalBool(std::optional smartInsertDelete); +#if !TARGET_OS_TV UIDataDetectorTypes RCTUITextViewDataDetectorTypesFromStringVector(const std::vector &dataDetectorTypes); +#endif NS_ASSUME_NONNULL_END diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm index 92467e2953f0cb..d53eee5271d43b 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm @@ -225,7 +225,7 @@ UITextContentType RCTUITextContentTypeFromString(const std::string &contentType) } #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 170000 /* __IPHONE_17_0 */ - if (@available(iOS 17.0, *)) { + if (@available(iOS 17.0, tvOS 17.0, *)) { [mutableContentTypeMap addEntriesFromDictionary:@{ @"creditCardExpiration" : UITextContentTypeCreditCardExpiration, @"creditCardExpirationMonth" : UITextContentTypeCreditCardExpirationMonth, @@ -271,6 +271,7 @@ UITextSmartInsertDeleteType RCTUITextSmartInsertDeleteTypeFromOptionalBool(std:: : UITextSmartInsertDeleteTypeDefault; } +#if !TARGET_OS_TV UIDataDetectorTypes RCTUITextViewDataDetectorTypesFromStringVector(const std::vector &dataDetectorTypes) { static dispatch_once_t onceToken; @@ -298,3 +299,4 @@ UIDataDetectorTypes RCTUITextViewDataDetectorTypesFromStringVector(const std::ve } return ret; } +#endif From f5092fddd79079efef46e751c74ff6a59fe1c655 Mon Sep 17 00:00:00 2001 From: Devan Buggay Date: Tue, 13 Jan 2026 13:47:08 -0800 Subject: [PATCH 4/7] RCTView compiling for AppleTV (#55130) Summary: Compile out unsupported UIKit APIs used by RCTView and it's manager. Changelog: [internal] Reviewed By: Abbondanzo Differential Revision: D90513795 --- packages/react-native/React/Views/RCTView.m | 3 ++- packages/react-native/React/Views/RCTViewManager.m | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/react-native/React/Views/RCTView.m b/packages/react-native/React/Views/RCTView.m index 0bc228f287d9e2..bc39a7212f7bc1 100644 --- a/packages/react-native/React/Views/RCTView.m +++ b/packages/react-native/React/Views/RCTView.m @@ -910,7 +910,8 @@ static void RCTUpdateShadowPathForView(RCTView *view) static void RCTUpdateHoverStyleForView(RCTView *view) { -#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 170000 /* __IPHONE_17_0 */ +#if !TARGET_OS_TV && defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && \ + __IPHONE_OS_VERSION_MAX_ALLOWED >= 170000 /* __IPHONE_17_0 */ if (@available(iOS 17.0, *)) { UIHoverStyle *hoverStyle = nil; if ([view cursor] == RCTCursorPointer) { diff --git a/packages/react-native/React/Views/RCTViewManager.m b/packages/react-native/React/Views/RCTViewManager.m index 1ace350d2422f0..fa1e0bcc95b9a2 100644 --- a/packages/react-native/React/Views/RCTViewManager.m +++ b/packages/react-native/React/Views/RCTViewManager.m @@ -295,6 +295,7 @@ - (void)updateAccessibilityTraitsForRole:(RCTView *)view withDefaultView:(RCTVie RCT_CUSTOM_VIEW_PROPERTY(accessibilityShowsLargeContentViewer, BOOL, RCTView) { +#if !TARGET_OS_TV if (@available(iOS 13.0, *)) { BOOL showsLargeContentViewer = json ? [RCTConvert BOOL:json] : defaultView.showsLargeContentViewer; @@ -306,13 +307,16 @@ - (void)updateAccessibilityTraitsForRole:(RCTView *)view withDefaultView:(RCTVie view.showsLargeContentViewer = NO; } } +#endif } RCT_CUSTOM_VIEW_PROPERTY(accessibilityLargeContentTitle, NSString, RCTView) { +#if !TARGET_OS_TV if (@available(iOS 13.0, *)) { view.largeContentTitle = json ? [RCTConvert NSString:json] : defaultView.largeContentTitle; } +#endif } RCT_CUSTOM_VIEW_PROPERTY(nativeID, NSString *, RCTView) From c32d01fde07b3ba300e1ea950ba923c5e57a4037 Mon Sep 17 00:00:00 2001 From: Devan Buggay Date: Tue, 13 Jan 2026 13:47:08 -0800 Subject: [PATCH 5/7] Replace systemBackgroundColor for AppleTV (#55131) Summary: [UIColor systemBackgroundColor] is unavailable on AppleTV, defaulting to [UIColor clearColor]. Changelog: [internal] Reviewed By: Abbondanzo Differential Revision: D90514013 --- .../AppDelegate/RCTDefaultReactNativeFactoryDelegate.mm | 4 ++++ .../Libraries/AppDelegate/RCTRootViewFactory.mm | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/packages/react-native/Libraries/AppDelegate/RCTDefaultReactNativeFactoryDelegate.mm b/packages/react-native/Libraries/AppDelegate/RCTDefaultReactNativeFactoryDelegate.mm index 3b917c1a05cc65..f780a89112794f 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTDefaultReactNativeFactoryDelegate.mm +++ b/packages/react-native/Libraries/AppDelegate/RCTDefaultReactNativeFactoryDelegate.mm @@ -59,7 +59,11 @@ - (UIView *)createRootViewWithBridge:(RCTBridge *)bridge { UIView *rootView = RCTAppSetupDefaultRootView(bridge, moduleName, initProps, YES); +#if TARGET_OS_TV + rootView.backgroundColor = [UIColor clearColor]; +#else rootView.backgroundColor = [UIColor systemBackgroundColor]; +#endif return rootView; } diff --git a/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.mm b/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.mm index 266d6bab211cdb..55b0a269848edf 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.mm +++ b/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.mm @@ -190,7 +190,11 @@ - (UIView *)viewWithModuleName:(NSString *)moduleName RCTSurfaceHostingProxyRootView *surfaceHostingProxyRootView = [[RCTSurfaceHostingProxyRootView alloc] initWithSurface:surface]; +#if TARGET_OS_TV + surfaceHostingProxyRootView.backgroundColor = [UIColor clearColor]; +#else surfaceHostingProxyRootView.backgroundColor = [UIColor systemBackgroundColor]; +#endif if (_configuration.customizeRootView != nil) { _configuration.customizeRootView(surfaceHostingProxyRootView); } @@ -207,7 +211,11 @@ - (UIView *)createRootViewWithBridge:(RCTBridge *)bridge initProps:(NSDictionary *)initProps { UIView *rootView = RCTAppSetupDefaultRootView(bridge, moduleName, initProps, YES); +#if !TARGET_OS_TV rootView.backgroundColor = [UIColor systemBackgroundColor]; +#else + rootView.backgroundColor = [UIColor blackColor]; +#endif return rootView; } From 4564d67b13b39e608ca831ef4a2001a371e3f810 Mon Sep 17 00:00:00 2001 From: Devan Buggay Date: Tue, 13 Jan 2026 13:47:08 -0800 Subject: [PATCH 6/7] Fix hardcoded notch marginTop in RNTester (#55132) Summary: RNTester doesnt use safe areas and instead hardcodes the margin top based on Platform.OS. Issue is that AppleTV returns `ios` so there is a gap since there is no notch. Instead of adding a new Metro platform, just using the deviceIdiom instead for now. Changelog: [internal] Reviewed By: Abbondanzo Differential Revision: D90514023 --- packages/rn-tester/js/components/RNTTitleBar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rn-tester/js/components/RNTTitleBar.js b/packages/rn-tester/js/components/RNTTitleBar.js index 4cb3d4f193b2a2..7ca8f9e6279fb7 100644 --- a/packages/rn-tester/js/components/RNTTitleBar.js +++ b/packages/rn-tester/js/components/RNTTitleBar.js @@ -98,7 +98,7 @@ const styles = StyleSheet.create({ header: { height: 40, flexDirection: 'row', - marginTop: Platform.OS === 'ios' ? 50 : 0, + marginTop: Platform.OS === 'ios' && !Platform.isTV ? 50 : 0, }, headerCenter: { flex: 1, From 6bb8f05d6636e818c64a3133ac931caff93d4550 Mon Sep 17 00:00:00 2001 From: Devan Buggay Date: Tue, 13 Jan 2026 13:47:08 -0800 Subject: [PATCH 7/7] Compile out UIFontTextStyleLargeTitle Summary: UIFontTextStyleLargeTitle is unavailable on AppleTV https://developer.apple.com/documentation/uikit/uifont/textstyle/largetitle?changes=_4 Reviewed By: javache Differential Revision: D90514242 --- .../Libraries/Text/Text/RCTDynamicTypeRamp.mm | 9 ++++++--- .../renderer/textlayoutmanager/RCTAttributedTextUtils.mm | 5 +++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/react-native/Libraries/Text/Text/RCTDynamicTypeRamp.mm b/packages/react-native/Libraries/Text/Text/RCTDynamicTypeRamp.mm index c28eada58e6a04..a3fe8e1e2b3593 100644 --- a/packages/react-native/Libraries/Text/Text/RCTDynamicTypeRamp.mm +++ b/packages/react-native/Libraries/Text/Text/RCTDynamicTypeRamp.mm @@ -34,7 +34,7 @@ @implementation RCTConvert (DynamicTypeRamp) static NSDictionary *mapping; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - mapping = @{ + NSMutableDictionary *mutableMapping = [@{ @(RCTDynamicTypeRampCaption2) : UIFontTextStyleCaption2, @(RCTDynamicTypeRampCaption1) : UIFontTextStyleCaption1, @(RCTDynamicTypeRampFootnote) : UIFontTextStyleFootnote, @@ -45,8 +45,11 @@ @implementation RCTConvert (DynamicTypeRamp) @(RCTDynamicTypeRampTitle3) : UIFontTextStyleTitle3, @(RCTDynamicTypeRampTitle2) : UIFontTextStyleTitle2, @(RCTDynamicTypeRampTitle1) : UIFontTextStyleTitle1, - @(RCTDynamicTypeRampLargeTitle) : UIFontTextStyleLargeTitle, - }; + } mutableCopy]; +#if !TARGET_OS_TV + mutableMapping[@(RCTDynamicTypeRampLargeTitle)] = UIFontTextStyleLargeTitle; +#endif + mapping = [mutableMapping copy]; }); id textStyle = diff --git a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm index 520a24c28f76bc..f96a0494000b37 100644 --- a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm +++ b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm @@ -60,7 +60,12 @@ inline static UIFontTextStyle RCTUIFontTextStyleForDynamicTypeRamp(const Dynamic case DynamicTypeRamp::Title1: return UIFontTextStyleTitle1; case DynamicTypeRamp::LargeTitle: +// UIFontTextStyleLargeTitle is not available on tvOS. +#if !TARGET_OS_TV return UIFontTextStyleLargeTitle; +#else + return UIFontTextStyleTitle1; +#endif } }