Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ NS_ASSUME_NONNULL_BEGIN

- (void)stopListeningToAnimatedNodeValue:(NSNumber *)tag;

- (NSSet<NSNumber *> *)getTagsOfConnectedNodesFrom:(NSNumber *)tag andEvent:(NSString *)eventName;

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -470,6 +479,24 @@ - (void)stepAnimations:(CADisplayLink *)displaylink
[self stopAnimationLoopIfNeeded];
}

- (NSSet<NSNumber *> *)getTagsOfConnectedNodesFrom:(NSNumber *)tag andEvent:(NSString *)eventName
{
NSMutableSet<NSNumber *> *tags = [NSMutableSet new];
NSString *key = [NSString stringWithFormat:@"%@%@", tag, RCTNormalizeAnimatedEventName(eventName)];
NSArray<RCTEventAnimation *> *eventAnimations = _eventDrivers[key];
for (RCTEventAnimation *animation in eventAnimations) {
NSNumber *nodeTag = [animation.valueNode nodeTag];
if (nodeTag) {
[tags addObject:nodeTag];
}
for (NSNumber *childNodeKey in [animation.valueNode childNodes]) {
[tags addObject:childNodeKey];
}
}

return tags;
}

#pragma mark-- Updates

- (void)updateAnimations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ @implementation RCTNativeAnimatedTurboModule {
NSMutableArray<AnimatedOperation> *_operations;
// Operations called before views have been updated.
NSMutableArray<AnimatedOperation> *_preOperations;

NSSet<NSString *> *_userDrivenAnimationEndedEvents;

// TODO: Remove this when https://github.com/facebook/react-native/pull/45457 lands
BOOL _shouldEmitEvent;
}

RCT_EXPORT_MODULE();
Expand All @@ -39,6 +44,8 @@ - (instancetype)init
if (self = [super init]) {
_operations = [NSMutableArray new];
_preOperations = [NSMutableArray new];
_userDrivenAnimationEndedEvents = [NSSet setWithArray:@[ @"onScrollEnded" ]];
_shouldEmitEvent = NO;
}
return self;
}
Expand Down Expand Up @@ -364,20 +371,52 @@ - (void)didMountComponentsWithRootTag:(NSInteger)rootTag

- (NSArray<NSString *> *)supportedEvents
{
return @[ @"onAnimatedValueUpdate" ];
return @[ @"onAnimatedValueUpdate", @"onUserDrivenAnimationEnded" ];
}

- (void)animatedNode:(RCTValueAnimatedNode *)node didUpdateValue:(CGFloat)value
{
[self sendEventWithName:@"onAnimatedValueUpdate" body:@{@"tag" : node.nodeTag, @"value" : @(value)}];
}

// TODO: Remove this when https://github.com/facebook/react-native/pull/45457 lands
- (void)startObserving
{
[super startObserving];
_shouldEmitEvent = YES;
}

- (void)stopObserving
{
[super stopObserving];
_shouldEmitEvent = NO;
}

// ----

- (void)userDrivenAnimationEnded:(NSArray<NSNumber *> *)nodes
{
if (!_shouldEmitEvent) {
return;
}

[self sendEventWithName:@"onUserDrivenAnimationEnded" body:@{@"tags" : nodes}];
}

- (void)eventDispatcherWillDispatchEvent:(id<RCTEvent>)event
{
// Events can be dispatched from any queue so we have to make sure handleAnimatedEvent
// is run from the main queue.
RCTExecuteOnMainQueue(^{
[self->_nodesManager handleAnimatedEvent:event];

if ([self->_userDrivenAnimationEndedEvents containsObject:event.eventName]) {
NSSet<NSNumber *> *tags = [self->_nodesManager getTagsOfConnectedNodesFrom:event.viewTag
andEvent:event.eventName];
if (tags.count > 0) {
[self userDrivenAnimationEnded:[tags allObjects]];
}
}
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -507,7 +512,7 @@ - (void)scrollViewDidScroll:(UIScrollView *)scrollView
static_cast<const ScrollViewEventEmitter &>(*_eventEmitter).onScroll(scrollMetrics);
}

RCTSendScrollEventForNativeAnimations_DEPRECATED(scrollView, self.tag);
RCTSendScrollEventForNativeAnimations_DEPRECATED(scrollView, self.tag, kOnScrollEvent);
}
}

Expand Down Expand Up @@ -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);
}
}

Expand All @@ -589,6 +595,8 @@ - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
static_cast<const ScrollViewEventEmitter &>(*_eventEmitter).onMomentumScrollEnd([self _scrollViewMetrics]);
[self _updateStateWithContentOffset];
_isUserTriggeredScrolling = NO;

RCTSendScrollEventForNativeAnimations_DEPRECATED(scrollView, self.tag, kOnScrollEndEvent);
}

- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
Expand Down