-
-
Notifications
You must be signed in to change notification settings - Fork 381
Conversation
Description
Implements shake gesture detection for the user feedback form. When useShakeGesture is enabled in SentryUserFeedbackConfiguration, shaking the device opens the feedback form.
The implementation swizzles UIWindow.motionEnded:withEvent: using class_addMethod + method_setImplementation to safely intercept shake events without modifying UIResponder's inherited implementation. A cooldown of 1 second prevents duplicate triggers.
Note: this is exposed to be used on React Native too getsentry/sentry-react-native#5754
Motivation and Context
The useShakeGesture property already existed in SentryUserFeedbackConfiguration but was never wired up. This PR implements the feature. The implementation is placed here (sentry-cocoa) rather than in each SDK separately so it can be reused by all SDKs that embed the feedback UI (React Native, Flutter, .NET MAUI, Unity).
How did you test it?
- Tested in a sample iOS app (release build) -- shaking the device opens the feedback form
- Debug builds: shake triggers the React Native dev menu (expected; RCTDevMenu also swizzles the same method)
- Unit tests added for
SentryShakeDetector
Checklist
- I added tests to verify the changes.
- No new PII added or SDK only sends newly added PII if
sendDefaultPIIis enabled. - I updated the docs if needed. docs(apple): Add shake-to-report section and note iOS-only support sentry-docs#16791
- I updated the wizard if needed.
- Review from the native team if needed.
- No breaking change or entry added to the changelog.
- No breaking change for hybrid SDKs or communicated to hybrid SDKs.
Closes #7597
This adds SentryShakeDetector which swizzles UIWindow.motionEnded:withEvent:
to detect shake gestures and wires it into SentryUserFeedbackIntegrationDriver.
Co-Authored-By: Claude Opus 4.6
Semver Impact of This PRMinor (new features) Changelog PreviewThis is how your changes will appear in the changelog. New Features
This preview updates automatically when you update the PR. |
|
Codecov Report Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #7579 +/- ## ============================================= - Coverage 85.288% 85.005% -0.284% ============================================= Files 483 484 +1 Lines 28747 28791 +44 Branches 12486 12505 +19 ============================================= - Hits 24518 24474 -44 - Misses 4182 4268 +86 - Partials 47 49 +2
... and 11 files with indirect coverage changes Continue to review full report in Codecov by Sentry.
|
Co-Authored-By: Claude Sonnet 4.6
The @interface declaration was wrapped in #if TARGET_OS_IOS, causing a
compile error on macOS/tvOS/watchOS where the @implementation in the
#else block could not find the interface. The class is now declared on
all platforms with the methods documented as no-ops on non-iOS.
Co-Authored-By: Claude Sonnet 4.6
atomic_bool for thread-safe enabled flag, bridged notification
constant, and unconditional cleanup in deinit.
Sources/Swift/Integrations/UserFeedback/SentryUserFeedbackIntegrationDriver.swift
Show resolved
Hide resolved
only call disable in deinit if it did. Prevents an old driver's
deallocation from disabling shake detection that a new driver
already re-enabled during SDK restart.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Sources/Swift/Integrations/UserFeedback/SentryUserFeedbackIntegrationDriver.swift
Show resolved
Hide resolved
apps). Move observeScreenshots and observeShakeGesture calls before
the return so shake detection works in SwiftUI apps.
Sources/Sentry/SentryShakeDetector.m
Outdated
| static void | ||
| sentry_motionEnded(UIWindow *self, SEL _cmd, UIEventSubtype motion, UIEvent *event) | ||
| { | ||
| if (atomic_load_explicit(&_shakeDetectionEnabled, memory_order_acquire) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Q: does this run on every shake after calling disable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, after disable, sentry_motionEnded still runs on every motionEnded:withEvent:, but it's a single atomic bool check that returns immediately. The swizzle is intentionally never removed (un-swizzling is unsafe with multiple swizzlers).
the `useShakeGesture` config description to note it's iOS-only.
Ref: getsentry/sentry-cocoa#7579
Co-Authored-By: Claude
| * Swizzling is performed at most once regardless of how many times @c +enable is called. | ||
| * On non-iOS platforms (macOS, tvOS, watchOS), these methods are no-ops. | ||
| */ | ||
| @interface SentryShakeDetector : NSObject |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
m: Any reason in particular to make it in ObjC?
I don't see anything that couldn't be done in Swift here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really other than initially implementing on the RN side and moving it here I'll convert to draft and move this to Swift. thank you for the feedback
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refactored with 3c24378
Sources/Sentry/SentryShakeDetector.m
Outdated
|
|
||
| #else | ||
|
|
||
| NSNotificationName const SentryShakeDetectedNotification = @"SentryShakeDetected"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
m: You can place this outside the #if TARGET_OS_IOS preprocessor directive to avoid declaring it twice
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tackled with the refactoring 3c24378
Sources/Sentry/SentryShakeDetector.m
Outdated
|
#import |
||
|
#import |
||
|
|
||
| #if TARGET_OS_IOS |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
h: iPad also supports shake gestures too
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tackled with the refactoring 3c24378
| } | ||
|
|
||
| @objc func handleShakeGesture() { | ||
| guard !displayingForm else { return } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
h: on iPad there may be several windows, and I am not sure if all of them get the shake gesture or not.
Will this present the feedback form on all of them?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tackled with the refactoring 3c24378
maintaining full ObjC compatibility via @objc annotations.
The class name remains SentryShakeDetector in ObjC, preserving
compatibility with sentry-react-native.
and regenerate sdk_api.json with Xcode 16.4.
Regenerate API snapshot accordingly.
| deinit { | ||
| customButton?.removeTarget(self, action: #selector(showForm(sender:)), for: .touchUpInside) | ||
| if didEnableShakeDetection { | ||
| SentryShakeDetector.disable() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
l: I think if it safe to always call disable