Skip to content

[ios] not calling activation callback on inactive gestures#3986

Open
akwasniewski wants to merge 15 commits intomainfrom
@akwasniewski/ios-fix-state-manager-begin
Open

[ios] not calling activation callback on inactive gestures#3986
akwasniewski wants to merge 15 commits intomainfrom
@akwasniewski/ios-fix-state-manager-begin

Conversation

@akwasniewski
Copy link
Contributor

Description

Gestures always call activation callback upon finalizing, regardless of the previous state. This is intended for normal gestures, however for manually handled gestures we don't want to call it. This PR unifies the behaviour across platforms, GestureManager.finalize() won't call activation callback.

Test plan

Tested on the state manager example

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes an issue where gestures were always calling the activation callback upon finalizing, regardless of their previous state. The fix ensures that manually handled gestures don't trigger the activation callback, unifying behavior across iOS and other platforms.

Changes:

  • Added a manualActivation parameter throughout the gesture handling call chain to distinguish manual state changes
  • Modified sendEventsInState to check both the parameter and instance variable before sending activation callbacks
  • Updated all gesture recognizer subclasses that override handleGesture:fromReset: to support the new parameter

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
RNGestureHandlerModule.mm Pass manualActivation:YES when manually setting gesture state via setGestureStateSync
RNGestureHandler.mm Add manualActivation parameter to gesture handling methods and update activation callback logic
RNGestureHandler.h Declare new method signatures with manualActivation parameter
RNRotationHandler.m Add overloaded handleGesture:fromReset:manualActivation: method
RNPinchHandler.m Add overloaded handleGesture:fromReset:manualActivation: method
RNLongPressHandler.m Add overloaded handleGesture:fromReset:manualActivation: method with platform-specific declarations
RNFlingHandler.m Add overloaded handleGesture:fromReset:manualActivation: method for macOS version

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

@m-bert m-bert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please merge recent updates? There were changes to handleGesture that should be considered in this PR

Base automatically changed from @akwasniewski/ios-state-manager-dont-rely-on-reset to main February 23, 2026 07:14
@akwasniewski akwasniewski requested a review from Copilot February 23, 2026 07:37
Comment on lines 143 to 144
[_gestureHandler handleGesture:self fromReset:fromReset manualActivation:NO];
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this override needed at all? It doesn't change anything compared to the base implementation

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes we need the default value one so that the selector works correctly (as in #3983) and we need to overload the one with all the arguements so that when called from stateManager it initializes the handler specific values (like previousTime in LongPress or scale in pinch).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the PR you linked, the base one ((void)handleGesture:(nonnull id)recognizer;) is implemented once in the base class. Here, handlers have both - the base one, and the extended one - defined. The base one is exactly the same as the implementation in GestureHandler. Hover doesn't seem to override either, and I assume the selector works there, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah right, I assume the override in Fling was used at some, but not anumore. Removed altogether in 1246b9d. The other overrides are needed though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are they needed? They don't differ from the default implementation.

Comment on lines 63 to 65
{
previousTime = CACurrentMediaTime();
[_gestureHandler handleGesture:recognizer fromReset:NO];
[self handleGesture:recognizer fromReset:fromReset manualActivation:NO];
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same

Comment on lines 46 to 48
{
[self handleGesture:recognizer fromReset:fromReset manualActivation:NO];
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same

Comment on lines 45 to 47
{
[self handleGesture:recognizer fromReset:fromReset manualActivation:NO];
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same

Comment on lines 416 to 418
} else if (
state == RNGestureHandlerStateEnd && _lastState != RNGestureHandlerStateActive && !manualActivation &&
!_manualActivation) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the end result here? onFinalize is invoked with didSucceed: true without going through activate/deactivate?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this unifies the behaviour across platforms. Previously we forced calling activation callback. Do we want to handle it differently?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if the user called onFinalize, rather than onFail he wants to have the onSuccess flag set to true (for whaterver reason), but if he wanted the activation callback he would have called StateManager.activate directly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean by

I think if the user called onFinalize, rather than onFail

I don't recall having onFail in that context.

Copy link
Contributor Author

@akwasniewski akwasniewski Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I meant when they called GestureStateManager.finalize() rather than GestureStateManager,fail()

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is didSucceed: true consistent across platforms here?

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

{
previousTime = CACurrentMediaTime();
[_gestureHandler handleGesture:recognizer fromReset:NO];
[_gestureHandler handleGesture:recognizer fromReset:NO manualActivation:NO];
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

previousTime is no longer updated in the handleGesture: action method, but getDuration relies on previousTime - startTime. Since handleGesture: is the selector registered as the recognizer's action, duration may stay near 0 or stale for the whole gesture. Consider updating previousTime here as well (or route handleGesture: through the fromReset:manualActivation: path that sets it).

Suggested change
[_gestureHandler handleGesture:recognizer fromReset:NO manualActivation:NO];
[self handleGesture:recognizer fromReset:NO manualActivation:NO];

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 138 to 143
return self;
}

- (void)handleGesture:(NSPanGestureRecognizer *)gestureRecognizer
{
[_gestureHandler handleGesture:self];
}

- (void)mouseDown:(NSEvent *)event
{
[super mouseDown:event];
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the macOS implementation of RNBetterSwipeGestureRecognizer, the recognizer is still created with initWithTarget:self action:@selector(handleGesture:), but handleGesture: is no longer present (it was removed in this change). When AppKit fires the action, this will crash with an unrecognized selector. Please re-add -handleGesture: to forward to _gestureHandler, or change the target/action to _gestureHandler like the iOS version.

Copilot uses AI. Check for mistakes.
Comment on lines 99 to 102
- (void)handleGesture:(nonnull id)recognizer fromReset:(BOOL)fromReset;
- (void)handleGesture:(nonnull id)recognizer fromReset:(BOOL)fromReset fromManual:(BOOL)fromManual;
- (void)handleGesture:(nonnull id)recognizer inState:(RNGestureHandlerState)state;
- (void)handleGesture:(nonnull id)recognizer inState:(RNGestureHandlerState)state fromManual:(BOOL)fromManual;
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new selector label fromManual: is ambiguous given the existing manualActivation flag on RNGestureHandler. It’s hard to tell whether this refers to “manual activation” vs “manual state changes via setGestureState”. Consider renaming this parameter/selector to something more specific (e.g. fromManualStateChange: or fromStateManager:) to avoid API confusion.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@m-bert what do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @copilot , my first thought was fromManualStateChange, but second one is also ok.

Copy link

Copilot AI commented Feb 25, 2026

@m-bert I've opened a new pull request, #3996, to work on those changes. Once the pull request is ready, I'll request review from you.

…dler API (#3996)

The `fromManual:` selector label was ambiguous — it could be confused
with the existing `manualActivation` flag on `RNGestureHandler`. Rename
it to `fromManualStateChange:` to clearly convey that this parameter
tracks state changes triggered via `setGestureState` (the manual state
manager path), not manual activation.

- **`RNGestureHandler.h`** — updated all three method declarations
- **`RNGestureHandler.mm`** — updated implementations and all call sites
- **`RNGestureHandlerModule.mm`** — updated `setGestureState` call sites
(the only places passing `YES`)
- **All handler files** (`RNFlingHandler`, `RNHoverHandler`,
`RNLongPressHandler`, `RNPanHandler`, `RNPinchHandler`,
`RNRotationHandler`, `RNTapHandler`) — updated forwarding declarations
and call sites

<!-- START COPILOT CODING AGENT TIPS -->
---

🔒 GitHub Advanced Security automatically protects Copilot coding agent
pull requests. You can protect all pull requests by enabling Advanced
Security for your repositories. [Learn more about Advanced
Security.](https://gh.io/cca-advanced-security)

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: m-bert <63123542+m-bert@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants