From f0802a6acab20ef604e38811b4f8539bbefbdeb0 Mon Sep 17 00:00:00 2001 From: Peter Abbondanzo Date: Tue, 12 Nov 2024 15:41:02 -0800 Subject: [PATCH] Fix momentum begin dispatch order (#47578) Summary: In `RCTScrollView`, it was possible that `onMomentumScrollBegin` gets queued after `onMomentumScrollEnd` because the event would dispatch after calling `setContentOffset`. As a result, the ScrollView JS gets stuck in an "animating" state and jest runners did not bother scrolling further. ## Changelog: [iOS][Fixed] - Momentum begin events now queue before momentum end events on scroll views Differential Revision: D65846878 --- .../ScrollView/RCTScrollViewComponentView.mm | 8 ++++++-- .../react-native/React/Views/ScrollView/RCTScrollView.m | 8 ++++++-- 2 files changed, 12 insertions(+), 4 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 48cdc2de8da46a..c56fa3aad16fd2 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm @@ -897,14 +897,18 @@ - (void)scrollToOffset:(CGPoint)offset animated:(BOOL)animated [self _forceDispatchNextScrollEvent]; + // Notify of momentum scroll begin before setting content offset or events can fire out of order and scrollview gets + // stuck in "animating" state + if (animated && _eventEmitter) { + static_cast(*_eventEmitter).onMomentumScrollBegin([self _scrollViewMetrics]); + } + [_scrollView setContentOffset:offset animated:animated]; if (!animated) { // When not animated, the expected workflow in ``scrollViewDidEndScrollingAnimation`` after scrolling is not going // to get triggered. We will need to manually execute here. [self _handleFinishedScrolling:_scrollView]; - } else if (_eventEmitter) { - static_cast(*_eventEmitter).onMomentumScrollBegin([self _scrollViewMetrics]); } } diff --git a/packages/react-native/React/Views/ScrollView/RCTScrollView.m b/packages/react-native/React/Views/ScrollView/RCTScrollView.m index 3fc9a47f3edd9a..f12138e778b84c 100644 --- a/packages/react-native/React/Views/ScrollView/RCTScrollView.m +++ b/packages/react-native/React/Views/ScrollView/RCTScrollView.m @@ -599,10 +599,12 @@ - (void)scrollToOffset:(CGPoint)offset animated:(BOOL)animated y = fmin(y, CGRectGetMaxY(maxRect)); offset = CGPointMake(x, y); } - [_scrollView setContentOffset:offset animated:animated]; + // Notify of momentum scroll begin before setting content offset or events can fire out of order and scrollview gets + // stuck in "animating" state if (animated) { [self sendScrollEventWithName:@"onMomentumScrollBegin" scrollView:_scrollView userData:nil]; } + [_scrollView setContentOffset:offset animated:animated]; } } @@ -624,10 +626,12 @@ - (void)scrollToEnd:(BOOL)animated if (!CGPointEqualToPoint(_scrollView.contentOffset, offset)) { // Ensure at least one scroll event will fire _allowNextScrollNoMatterWhat = YES; - [_scrollView setContentOffset:offset animated:animated]; + // Notify of momentum scroll begin before setting content offset or events can fire out of order and scrollview gets + // stuck in "animating" state if (animated) { [self sendScrollEventWithName:@"onMomentumScrollBegin" scrollView:_scrollView userData:nil]; } + [_scrollView setContentOffset:offset animated:animated]; } }