diff --git a/packages/react-native/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.mm b/packages/react-native/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.mm index b3669766ea07..c195025f3168 100644 --- a/packages/react-native/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.mm +++ b/packages/react-native/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.mm @@ -368,6 +368,15 @@ - (void)addAnimatedEventToView:(NSNumber *)viewTag [drivers addObject:driver]; _eventDrivers[key] = drivers; } + + // Handle onScrollEnded special events. + // These are triggered when the user stops dragging or when the + // scroll view stops decelerating after the user swiped + // The goal is to use this event to force a resync of the Shadow Tree + // with the Native tree + if ([eventName isEqualToString:@"onScroll"]) { + [self addAnimatedEventToView:viewTag eventName:@"onScrollEnded" eventMapping:eventMapping]; + } } - (void)removeAnimatedEventFromView:(NSNumber *)viewTag 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 98cf04959323..cc81ad3a45cd 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm @@ -26,6 +26,10 @@ using namespace facebook::react; +static NSString *kOnScrollEvent = @"onScroll"; + +static NSString *kOnScrollEndEvent = @"onScrollEnded"; + static const CGFloat kClippingLeeway = 44.0; static UIScrollViewKeyboardDismissMode RCTUIKeyboardDismissModeFromProps(const ScrollViewProps &props) @@ -56,10 +60,11 @@ static UIScrollViewIndicatorStyle RCTUIScrollViewIndicatorStyleFromProps(const S // This is just a workaround to allow animations based on onScroll event. // This is only used to animate sticky headers in ScrollViews, and only the contentOffset and tag is used. // TODO: T116850910 [Fabric][iOS] Make Fabric not use legacy RCTEventDispatcher for native-driven AnimatedEvents -static void RCTSendScrollEventForNativeAnimations_DEPRECATED(UIScrollView *scrollView, NSInteger tag) +static void +RCTSendScrollEventForNativeAnimations_DEPRECATED(UIScrollView *scrollView, NSInteger tag, NSString *eventName) { static uint16_t coalescingKey = 0; - RCTScrollEvent *scrollEvent = [[RCTScrollEvent alloc] initWithEventName:@"onScroll" + RCTScrollEvent *scrollEvent = [[RCTScrollEvent alloc] initWithEventName:eventName reactTag:[NSNumber numberWithInt:tag] scrollViewContentOffset:scrollView.contentOffset scrollViewContentInset:scrollView.contentInset @@ -507,7 +512,7 @@ - (void)scrollViewDidScroll:(UIScrollView *)scrollView static_cast(*_eventEmitter).onScroll(scrollMetrics); } - RCTSendScrollEventForNativeAnimations_DEPRECATED(scrollView, self.tag); + RCTSendScrollEventForNativeAnimations_DEPRECATED(scrollView, self.tag, kOnScrollEvent); } } @@ -564,6 +569,7 @@ - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL // ScrollView will not decelerate and `scrollViewDidEndDecelerating` will not be called. // `_isUserTriggeredScrolling` must be set to NO here. _isUserTriggeredScrolling = NO; + RCTSendScrollEventForNativeAnimations_DEPRECATED(scrollView, self.tag, kOnScrollEndEvent); } } @@ -589,6 +595,8 @@ - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView static_cast(*_eventEmitter).onMomentumScrollEnd([self _scrollViewMetrics]); [self _updateStateWithContentOffset]; _isUserTriggeredScrolling = NO; + + RCTSendScrollEventForNativeAnimations_DEPRECATED(scrollView, self.tag, kOnScrollEndEvent); } - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView