diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTEnhancedScrollView.h b/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTEnhancedScrollView.h index 6402bc6c86c1..0fec272855f0 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTEnhancedScrollView.h +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTEnhancedScrollView.h @@ -12,6 +12,14 @@ NS_ASSUME_NONNULL_BEGIN +/* + * Returns the default `contentInsetAdjustmentBehavior` to apply when the JS + * prop is not set. On iOS 26+ (liquid glass) it defaults to `scrollableAxes` so + * scroll views adjust their content for translucent system chrome, unless the + * app opts into `UIDesignRequiresCompatibility`. Otherwise it stays `never`. + */ +UIScrollViewContentInsetAdjustmentBehavior RCTDefaultContentInsetAdjustmentBehavior(void); + /* * Many `UIScrollView` customizations normally require creating a subclass which is not always convenient. * `RCTEnhancedScrollView` has a delegate (conforming to this protocol) that allows customizing such behaviors without diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTEnhancedScrollView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTEnhancedScrollView.mm index b3481c1b98b4..2829d4b2826a 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTEnhancedScrollView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTEnhancedScrollView.mm @@ -9,6 +9,23 @@ #import #import +UIScrollViewContentInsetAdjustmentBehavior RCTDefaultContentInsetAdjustmentBehavior(void) +{ + // On iOS 26+, Apple's liquid glass design uses translucent system chrome + // (tab bars, toolbars), so scroll views should adjust their content insets + // for it unless the app opted into compatibility mode. + if (@available(iOS 26, *)) { + NSNumber *requiresCompatibility = + [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIDesignRequiresCompatibility"]; + if (!requiresCompatibility.boolValue) { + return UIScrollViewContentInsetAdjustmentScrollableAxes; + } + } + // Pre-iOS 26 (or compatibility mode): keep the historical opt-in behavior so + // iOS doesn't do weird things to UIScrollView insets automatically. + return UIScrollViewContentInsetAdjustmentNever; +} + @interface RCTEnhancedScrollView () @end @@ -31,10 +48,7 @@ + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { - // We set the default behavior to "never" so that iOS - // doesn't do weird things to UIScrollView insets automatically - // and keeps it as an opt-in behavior. - self.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; + self.contentInsetAdjustmentBehavior = RCTDefaultContentInsetAdjustmentBehavior(); // We intentionally force `UIScrollView`s `semanticContentAttribute` to `LTR` here // because this attribute affects a position of vertical scrollbar; we don't want this diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm index 548987ff291d..52ac8fdac372 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm @@ -436,15 +436,26 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & if ((oldScrollViewProps.contentInsetAdjustmentBehavior != newScrollViewProps.contentInsetAdjustmentBehavior) || _shouldUpdateContentInsetAdjustmentBehavior) { - const auto contentInsetAdjustmentBehavior = newScrollViewProps.contentInsetAdjustmentBehavior; - if (contentInsetAdjustmentBehavior == ContentInsetAdjustmentBehavior::Never) { - scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; - } else if (contentInsetAdjustmentBehavior == ContentInsetAdjustmentBehavior::Automatic) { - scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentAutomatic; - } else if (contentInsetAdjustmentBehavior == ContentInsetAdjustmentBehavior::ScrollableAxes) { - scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentScrollableAxes; - } else if (contentInsetAdjustmentBehavior == ContentInsetAdjustmentBehavior::Always) { - scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentAlways; + // `nullopt` means the prop was not set from JS, so fall back to the host + // platform default (which adapts to iOS 26 liquid glass). An explicit value + // from JS - including `never` - is always honored. + if (newScrollViewProps.contentInsetAdjustmentBehavior.has_value()) { + switch (*newScrollViewProps.contentInsetAdjustmentBehavior) { + case ContentInsetAdjustmentBehavior::Never: + scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; + break; + case ContentInsetAdjustmentBehavior::Automatic: + scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentAutomatic; + break; + case ContentInsetAdjustmentBehavior::ScrollableAxes: + scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentScrollableAxes; + break; + case ContentInsetAdjustmentBehavior::Always: + scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentAlways; + break; + } + } else { + scrollView.contentInsetAdjustmentBehavior = RCTDefaultContentInsetAdjustmentBehavior(); } _shouldUpdateContentInsetAdjustmentBehavior = NO; } @@ -698,10 +709,7 @@ - (void)prepareForRecycle _contentSize = CGSizeZero; // Reset contentInset to prevent stale insets leaking into recycled scroll views. _scrollView.contentInset = UIEdgeInsetsZero; - // We set the default behavior to "never" so that iOS - // doesn't do weird things to UIScrollView insets automatically - // and keeps it as an opt-in behavior. - _scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; + _scrollView.contentInsetAdjustmentBehavior = RCTDefaultContentInsetAdjustmentBehavior(); _shouldUpdateContentInsetAdjustmentBehavior = YES; _isUserTriggeredScrolling = NO; CGRect oldFrame = self.frame; diff --git a/packages/react-native/ReactCommon/react/renderer/components/scrollview/BaseScrollViewProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/scrollview/BaseScrollViewProps.cpp index a683cb87f825..bdede5b39d53 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/scrollview/BaseScrollViewProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/scrollview/BaseScrollViewProps.cpp @@ -354,7 +354,7 @@ BaseScrollViewProps::BaseScrollViewProps( rawProps, "contentInsetAdjustmentBehavior", sourceProps.contentInsetAdjustmentBehavior, - {ContentInsetAdjustmentBehavior::Never})), + {})), scrollToOverflowEnabled( ReactNativeFeatureFlags::enableCppPropsIteratorSetter() ? sourceProps.scrollToOverflowEnabled diff --git a/packages/react-native/ReactCommon/react/renderer/components/scrollview/BaseScrollViewProps.h b/packages/react-native/ReactCommon/react/renderer/components/scrollview/BaseScrollViewProps.h index f6de1cd07fd5..2bd2fa2cf9a6 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/scrollview/BaseScrollViewProps.h +++ b/packages/react-native/ReactCommon/react/renderer/components/scrollview/BaseScrollViewProps.h @@ -65,7 +65,10 @@ class BaseScrollViewProps : public ViewProps { std::vector snapToOffsets{}; bool snapToStart{true}; bool snapToEnd{true}; - ContentInsetAdjustmentBehavior contentInsetAdjustmentBehavior{ContentInsetAdjustmentBehavior::Never}; + // `nullopt` means the prop was not set from JS, in which case the host + // platform decides the default (e.g. on iOS 26+ liquid glass defaults to + // `scrollableAxes`). An explicit JS value (including `never`) is preserved. + std::optional contentInsetAdjustmentBehavior{}; bool scrollToOverflowEnabled{false}; bool isInvertedVirtualizedList{false}; diff --git a/packages/react-native/ReactCommon/react/renderer/components/scrollview/platform/android/react/renderer/components/scrollview/HostPlatformScrollViewProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/scrollview/platform/android/react/renderer/components/scrollview/HostPlatformScrollViewProps.cpp index 7ef93d229526..733bcd79665b 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/scrollview/platform/android/react/renderer/components/scrollview/HostPlatformScrollViewProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/scrollview/platform/android/react/renderer/components/scrollview/HostPlatformScrollViewProps.cpp @@ -352,19 +352,21 @@ folly::dynamic HostPlatformScrollViewProps::getDiffProps( if (contentInsetAdjustmentBehavior != oldProps->contentInsetAdjustmentBehavior) { - switch (contentInsetAdjustmentBehavior) { - case ContentInsetAdjustmentBehavior::Never: - result["contentInsetAdjustmentBehavior"] = "never"; - break; - case ContentInsetAdjustmentBehavior::Automatic: - result["contentInsetAdjustmentBehavior"] = "automatic"; - break; - case ContentInsetAdjustmentBehavior::ScrollableAxes: - result["contentInsetAdjustmentBehavior"] = "scrollableAxes"; - break; - case ContentInsetAdjustmentBehavior::Always: - result["contentInsetAdjustmentBehavior"] = "always"; - break; + if (contentInsetAdjustmentBehavior.has_value()) { + switch (*contentInsetAdjustmentBehavior) { + case ContentInsetAdjustmentBehavior::Never: + result["contentInsetAdjustmentBehavior"] = "never"; + break; + case ContentInsetAdjustmentBehavior::Automatic: + result["contentInsetAdjustmentBehavior"] = "automatic"; + break; + case ContentInsetAdjustmentBehavior::ScrollableAxes: + result["contentInsetAdjustmentBehavior"] = "scrollableAxes"; + break; + case ContentInsetAdjustmentBehavior::Always: + result["contentInsetAdjustmentBehavior"] = "always"; + break; + } } } diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index c58793426e3a..b5330440442c 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -1728,7 +1728,6 @@ class facebook::react::BaseScrollViewProps : public facebook::react::HostPlatfor public bool showsVerticalScrollIndicator; public bool snapToEnd; public bool snapToStart; - public facebook::react::ContentInsetAdjustmentBehavior contentInsetAdjustmentBehavior; public facebook::react::EdgeInsets contentInset; public facebook::react::EdgeInsets scrollIndicatorInsets; public facebook::react::Float decelerationRate; @@ -1742,6 +1741,7 @@ class facebook::react::BaseScrollViewProps : public facebook::react::HostPlatfor public facebook::react::ScrollViewIndicatorStyle indicatorStyle; public facebook::react::ScrollViewKeyboardDismissMode keyboardDismissMode; public facebook::react::ScrollViewSnapToAlignment snapToAlignment; + public std::optional contentInsetAdjustmentBehavior; public std::optional maintainVisibleContentPosition; public std::vector snapToOffsets; public void setProp(const facebook::react::PropsParserContext& context, facebook::react::RawPropsPropNameHash hash, const char* propName, const facebook::react::RawValue& value); diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api index c6855293991d..3c96f8d80de9 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api @@ -1722,7 +1722,6 @@ class facebook::react::BaseScrollViewProps : public facebook::react::HostPlatfor public bool showsVerticalScrollIndicator; public bool snapToEnd; public bool snapToStart; - public facebook::react::ContentInsetAdjustmentBehavior contentInsetAdjustmentBehavior; public facebook::react::EdgeInsets contentInset; public facebook::react::EdgeInsets scrollIndicatorInsets; public facebook::react::Float decelerationRate; @@ -1736,6 +1735,7 @@ class facebook::react::BaseScrollViewProps : public facebook::react::HostPlatfor public facebook::react::ScrollViewIndicatorStyle indicatorStyle; public facebook::react::ScrollViewKeyboardDismissMode keyboardDismissMode; public facebook::react::ScrollViewSnapToAlignment snapToAlignment; + public std::optional contentInsetAdjustmentBehavior; public std::optional maintainVisibleContentPosition; public std::vector snapToOffsets; public void setProp(const facebook::react::PropsParserContext& context, facebook::react::RawPropsPropNameHash hash, const char* propName, const facebook::react::RawValue& value); diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index e39de8ef0e33..4af8c2aac158 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -1726,7 +1726,6 @@ class facebook::react::BaseScrollViewProps : public facebook::react::HostPlatfor public bool showsVerticalScrollIndicator; public bool snapToEnd; public bool snapToStart; - public facebook::react::ContentInsetAdjustmentBehavior contentInsetAdjustmentBehavior; public facebook::react::EdgeInsets contentInset; public facebook::react::EdgeInsets scrollIndicatorInsets; public facebook::react::Float decelerationRate; @@ -1740,6 +1739,7 @@ class facebook::react::BaseScrollViewProps : public facebook::react::HostPlatfor public facebook::react::ScrollViewIndicatorStyle indicatorStyle; public facebook::react::ScrollViewKeyboardDismissMode keyboardDismissMode; public facebook::react::ScrollViewSnapToAlignment snapToAlignment; + public std::optional contentInsetAdjustmentBehavior; public std::optional maintainVisibleContentPosition; public std::vector snapToOffsets; public void setProp(const facebook::react::PropsParserContext& context, facebook::react::RawPropsPropNameHash hash, const char* propName, const facebook::react::RawValue& value); diff --git a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api index e4afb026c956..1fbe374c11a3 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api @@ -4317,7 +4317,6 @@ class facebook::react::BaseScrollViewProps : public facebook::react::HostPlatfor public bool showsVerticalScrollIndicator; public bool snapToEnd; public bool snapToStart; - public facebook::react::ContentInsetAdjustmentBehavior contentInsetAdjustmentBehavior; public facebook::react::EdgeInsets contentInset; public facebook::react::EdgeInsets scrollIndicatorInsets; public facebook::react::Float decelerationRate; @@ -4331,6 +4330,7 @@ class facebook::react::BaseScrollViewProps : public facebook::react::HostPlatfor public facebook::react::ScrollViewIndicatorStyle indicatorStyle; public facebook::react::ScrollViewKeyboardDismissMode keyboardDismissMode; public facebook::react::ScrollViewSnapToAlignment snapToAlignment; + public std::optional contentInsetAdjustmentBehavior; public std::optional maintainVisibleContentPosition; public std::vector snapToOffsets; public void setProp(const facebook::react::PropsParserContext& context, facebook::react::RawPropsPropNameHash hash, const char* propName, const facebook::react::RawValue& value); diff --git a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api index 8b369a29aee5..8f204cd4a6ab 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api @@ -4300,7 +4300,6 @@ class facebook::react::BaseScrollViewProps : public facebook::react::HostPlatfor public bool showsVerticalScrollIndicator; public bool snapToEnd; public bool snapToStart; - public facebook::react::ContentInsetAdjustmentBehavior contentInsetAdjustmentBehavior; public facebook::react::EdgeInsets contentInset; public facebook::react::EdgeInsets scrollIndicatorInsets; public facebook::react::Float decelerationRate; @@ -4314,6 +4313,7 @@ class facebook::react::BaseScrollViewProps : public facebook::react::HostPlatfor public facebook::react::ScrollViewIndicatorStyle indicatorStyle; public facebook::react::ScrollViewKeyboardDismissMode keyboardDismissMode; public facebook::react::ScrollViewSnapToAlignment snapToAlignment; + public std::optional contentInsetAdjustmentBehavior; public std::optional maintainVisibleContentPosition; public std::vector snapToOffsets; public void setProp(const facebook::react::PropsParserContext& context, facebook::react::RawPropsPropNameHash hash, const char* propName, const facebook::react::RawValue& value); diff --git a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api index 2dcd88a4283f..98cbd0ce1cba 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api @@ -4315,7 +4315,6 @@ class facebook::react::BaseScrollViewProps : public facebook::react::HostPlatfor public bool showsVerticalScrollIndicator; public bool snapToEnd; public bool snapToStart; - public facebook::react::ContentInsetAdjustmentBehavior contentInsetAdjustmentBehavior; public facebook::react::EdgeInsets contentInset; public facebook::react::EdgeInsets scrollIndicatorInsets; public facebook::react::Float decelerationRate; @@ -4329,6 +4328,7 @@ class facebook::react::BaseScrollViewProps : public facebook::react::HostPlatfor public facebook::react::ScrollViewIndicatorStyle indicatorStyle; public facebook::react::ScrollViewKeyboardDismissMode keyboardDismissMode; public facebook::react::ScrollViewSnapToAlignment snapToAlignment; + public std::optional contentInsetAdjustmentBehavior; public std::optional maintainVisibleContentPosition; public std::vector snapToOffsets; public void setProp(const facebook::react::PropsParserContext& context, facebook::react::RawPropsPropNameHash hash, const char* propName, const facebook::react::RawValue& value); diff --git a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api index 7bb8f3c808d3..f88dcd428703 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api @@ -1060,13 +1060,13 @@ class facebook::react::BaseScrollViewProps : public facebook::react::HostPlatfor public bool showsVerticalScrollIndicator; public bool snapToEnd; public bool snapToStart; - public facebook::react::ContentInsetAdjustmentBehavior contentInsetAdjustmentBehavior; public facebook::react::EdgeInsets contentInset; public facebook::react::EdgeInsets scrollIndicatorInsets; public facebook::react::Point contentOffset; public facebook::react::ScrollViewIndicatorStyle indicatorStyle; public facebook::react::ScrollViewKeyboardDismissMode keyboardDismissMode; public facebook::react::ScrollViewSnapToAlignment snapToAlignment; + public std::optional contentInsetAdjustmentBehavior; public std::optional maintainVisibleContentPosition; public std::vector snapToOffsets; public void setProp(const facebook::react::PropsParserContext& context, facebook::react::RawPropsPropNameHash hash, const char* propName, const facebook::react::RawValue& value); diff --git a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api index 11d1e36952ef..bb7a7e45dda7 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api @@ -1055,13 +1055,13 @@ class facebook::react::BaseScrollViewProps : public facebook::react::HostPlatfor public bool showsVerticalScrollIndicator; public bool snapToEnd; public bool snapToStart; - public facebook::react::ContentInsetAdjustmentBehavior contentInsetAdjustmentBehavior; public facebook::react::EdgeInsets contentInset; public facebook::react::EdgeInsets scrollIndicatorInsets; public facebook::react::Point contentOffset; public facebook::react::ScrollViewIndicatorStyle indicatorStyle; public facebook::react::ScrollViewKeyboardDismissMode keyboardDismissMode; public facebook::react::ScrollViewSnapToAlignment snapToAlignment; + public std::optional contentInsetAdjustmentBehavior; public std::optional maintainVisibleContentPosition; public std::vector snapToOffsets; public void setProp(const facebook::react::PropsParserContext& context, facebook::react::RawPropsPropNameHash hash, const char* propName, const facebook::react::RawValue& value); diff --git a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api index 6d7c15d11ef2..44adaec77598 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api @@ -1058,13 +1058,13 @@ class facebook::react::BaseScrollViewProps : public facebook::react::HostPlatfor public bool showsVerticalScrollIndicator; public bool snapToEnd; public bool snapToStart; - public facebook::react::ContentInsetAdjustmentBehavior contentInsetAdjustmentBehavior; public facebook::react::EdgeInsets contentInset; public facebook::react::EdgeInsets scrollIndicatorInsets; public facebook::react::Point contentOffset; public facebook::react::ScrollViewIndicatorStyle indicatorStyle; public facebook::react::ScrollViewKeyboardDismissMode keyboardDismissMode; public facebook::react::ScrollViewSnapToAlignment snapToAlignment; + public std::optional contentInsetAdjustmentBehavior; public std::optional maintainVisibleContentPosition; public std::vector snapToOffsets; public void setProp(const facebook::react::PropsParserContext& context, facebook::react::RawPropsPropNameHash hash, const char* propName, const facebook::react::RawValue& value);