From 4f8a8ce316494db99b19f6c8db6b0c1e7b6500d9 Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Mon, 21 Aug 2023 18:03:43 -0700 Subject: [PATCH] Fix inverted `contentOffset` in scroll events in RTL (#39031) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/39031 `UIScrollView` `contentOffset` is flow-relative, so `x` is relative to the right edge of the screen. This coordinate space disagrees with layout events, `scrollTo` coordinates, and other platforms. This applies the same logic we use for inverting `scrollTo` coordinates to invert `contentOffset` in scroll events, in both Paper and Fabric. We then remove the iOS specific workaround we have in VirtualizedList. I did not test `contentInset` as part of this, whose structure has explicit edges like `left` and `right`. Changelog: [iOS][Fixed] - Fix inverted `contentOffset` in scroll events in RTL Reviewed By: rozele Differential Revision: D48379915 fbshipit-source-id: 8a9cbb01608e79ef3b179a76fbe3997a0cd23845 --- .../ScrollView/RCTScrollViewComponentView.mm | 5 +++++ .../React/Views/ScrollView/RCTScrollView.m | 8 +++++++- .../virtualized-lists/Lists/VirtualizedList.js | 14 +++++++------- 3 files changed, 19 insertions(+), 8 deletions(-) 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 b8894132b77619..add5f743eb7aa0 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm @@ -390,6 +390,11 @@ - (ScrollViewMetrics)_scrollViewMetrics metrics.contentInset = RCTEdgeInsetsFromUIEdgeInsets(_scrollView.contentInset); metrics.containerSize = RCTSizeFromCGSize(_scrollView.bounds.size); metrics.zoomScale = _scrollView.zoomScale; + + if (_layoutMetrics.layoutDirection == LayoutDirection::RightToLeft) { + metrics.contentOffset.x = metrics.contentSize.width - metrics.containerSize.width - metrics.contentOffset.x; + } + return metrics; } diff --git a/packages/react-native/React/Views/ScrollView/RCTScrollView.m b/packages/react-native/React/Views/ScrollView/RCTScrollView.m index 6bd4b708983908..16f300b97668d0 100644 --- a/packages/react-native/React/Views/ScrollView/RCTScrollView.m +++ b/packages/react-native/React/Views/ScrollView/RCTScrollView.m @@ -1038,9 +1038,15 @@ - (void)sendScrollEventWithName:(NSString *)eventName _coalescingKey++; _lastEmittedEventName = [eventName copy]; } + + CGPoint offset = scrollView.contentOffset; + if ([UIApplication sharedApplication].userInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) { + offset.x = scrollView.contentSize.width - scrollView.frame.size.width - offset.x; + } + RCTScrollEvent *scrollEvent = [[RCTScrollEvent alloc] initWithEventName:eventName reactTag:self.reactTag - scrollViewContentOffset:scrollView.contentOffset + scrollViewContentOffset:offset scrollViewContentInset:scrollView.contentInset scrollViewContentSize:scrollView.contentSize scrollViewFrame:scrollView.frame diff --git a/packages/virtualized-lists/Lists/VirtualizedList.js b/packages/virtualized-lists/Lists/VirtualizedList.js index e5ac5773d4d323..0306925242d145 100644 --- a/packages/virtualized-lists/Lists/VirtualizedList.js +++ b/packages/virtualized-lists/Lists/VirtualizedList.js @@ -1735,15 +1735,15 @@ class VirtualizedList extends StateSafePureComponent { _offsetFromScrollEvent(e: ScrollEvent): number { const {contentOffset, contentSize, layoutMeasurement} = e.nativeEvent; const {horizontal, rtl} = this._orientation(); - if (Platform.OS === 'ios' || !(horizontal && rtl)) { + if (horizontal && rtl) { + return ( + this._selectLength(contentSize) - + (this._selectOffset(contentOffset) + + this._selectLength(layoutMeasurement)) + ); + } else { return this._selectOffset(contentOffset); } - - return ( - this._selectLength(contentSize) - - (this._selectOffset(contentOffset) + - this._selectLength(layoutMeasurement)) - ); } _scheduleCellsToRenderUpdate(opts?: {allowImmediateExecution?: boolean}) {