Skip to content

Commit 41fbd37

Browse files
[iOS] Distinguish between mouse and stylus when hovering (#3991)
## Description <!-- Description and motivation for this PR. Include 'Fixes #<number>' if this is fixing some issue. --> Improves the hover gesture on iOS by distinguishing between a mouse and a stylus, if possible. With these changes the handler will now assume a mouse (just like previously with the enum bug), but if `zOffset` is available (iOS 16.1) and it is over 0.0 it will instead be recognised as styus. ~*Maybe it makes more sense to invert the logic and assume a mouse as default, and stylus if > 0. Let me know what you think.*~ See #3977 for more context. Fixes #3977 ## Test plan <!-- Describe how did you test this change here. --> Here's a small test page that can be added to the basic-example app for testing; ```jsx import { useState } from 'react'; import { View, Text } from 'react-native'; import { GestureDetector, GestureHandlerRootView, useHoverGesture, useSimultaneousGestures, useTapGesture, } from 'react-native-gesture-handler'; import { runOnJS } from 'react-native-worklets'; const POINTER_TYPES: Record<number, string> = { 0: "TOUCH", 1: "STYLUS", 2: "MOUSE", 3: "KEY", 4: "OTHER", } export default function Hover() { const [hoverPointerType, setHoverPointerType] = useState(-1); const [tapPointerType, setTapPointerType] = useState(-1); const hoverGesture = useHoverGesture({ onUpdate: (event) => { runOnJS(setHoverPointerType)(event.pointerType); }, }); const tapGesture = useTapGesture({ onFinalize: (event) => { runOnJS(setTapPointerType)(event.pointerType); }, }); const gesture = useSimultaneousGestures(hoverGesture, tapGesture); return ( <GestureHandlerRootView style={{ flex: 1, backgroundColor: 'white', justifyContent: 'center', alignItems: 'center', }}> <Text>Hover pointer type: {POINTER_TYPES[hoverPointerType] ?? "N/A"}</Text> <Text>Tap pointer type: {POINTER_TYPES[tapPointerType] ?? "N/A"}</Text> <GestureDetector gesture={gesture}> <View style={{ width: 200, height: 200, backgroundColor: 'salmon' }}></View> </GestureDetector> </GestureHandlerRootView> ); } ``` Mouse is testable in the iPad simulator by enabling `I/O -> Input -> Send Pointer to Device` in the top menus. Mouse is also testable on a real iPad by Linking keyboard and mouse to it from a mac in the Displays settings. Apple Pencil is only testable with an actual pencil on a real iPad. --------- Co-authored-by: Michał <michal.bert@swmansion.com>
1 parent 7406046 commit 41fbd37

12 files changed

Lines changed: 41 additions & 14 deletions

packages/react-native-gesture-handler/apple/Handlers/RNFlingHandler.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ - (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler
2626

2727
- (void)touchesBegan:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
2828
{
29-
[_gestureHandler setCurrentPointerType:event];
29+
[_gestureHandler setCurrentPointerTypeForEvent:event];
3030
_lastPoint = [[[touches allObjects] objectAtIndex:0] locationInView:_gestureHandler.recognizer.view];
3131
[_gestureHandler reset];
3232
[super touchesBegan:touches withEvent:event];

packages/react-native-gesture-handler/apple/Handlers/RNForceTouchHandler.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ - (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler
4040

4141
- (void)touchesBegan:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
4242
{
43-
[_gestureHandler setCurrentPointerType:event];
43+
[_gestureHandler setCurrentPointerTypeForEvent:event];
4444
if (_firstTouch) {
4545
// ignore rest of fingers
4646
return;

packages/react-native-gesture-handler/apple/Handlers/RNHoverHandler.m

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,22 @@ @implementation RNBetterHoverGestureRecognizer {
4040

4141
- (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler
4242
{
43-
if ((self = [super initWithTarget:gestureHandler action:@selector(handleGesture:)])) {
43+
if ((self = [super initWithTarget:self action:@selector(handleGesture:)])) {
4444
_gestureHandler = gestureHandler;
4545
_hoverEffect = RNGestureHandlerHoverEffectNone;
4646
}
4747
return self;
4848
}
4949

50+
- (void)handleGesture:(UIHoverGestureRecognizer *)recognizer
51+
{
52+
if (recognizer.state == UIGestureRecognizerStateBegan) {
53+
[_gestureHandler setCurrentPointerType:RNGestureHandlerMouse];
54+
}
55+
56+
[_gestureHandler handleGesture:self];
57+
}
58+
5059
- (void)triggerAction
5160
{
5261
[_gestureHandler handleGesture:self fromReset:NO];
@@ -153,11 +162,22 @@ - (void)updateConfig:(NSDictionary *)config
153162
#endif
154163
}
155164

165+
- (void)setCurrentPointerType:(RNGestureHandlerPointerType)pointerType
166+
{
167+
_pointerType = pointerType;
168+
169+
if (@available(iOS 16.1, *)) {
170+
if (((UIHoverGestureRecognizer *)self.recognizer).zOffset > 0.0) {
171+
_pointerType = RNGestureHandlerStylus;
172+
}
173+
}
174+
}
175+
156176
- (RNGestureHandlerEventExtraData *)eventExtraData:(UIGestureRecognizer *)recognizer
157177
{
158178
return [RNGestureHandlerEventExtraData forPosition:[recognizer locationInView:recognizer.view]
159179
withAbsolutePosition:[recognizer locationInView:recognizer.view.window]
160-
withPointerType:RNGestureHandlerStylus];
180+
withPointerType:_pointerType];
161181
}
162182

163183
@end
@@ -173,6 +193,7 @@ - (instancetype)initWithTag:(NSNumber *)tag
173193
{
174194
if ((self = [super initWithTag:tag])) {
175195
_recognizer = [NSGestureRecognizer alloc];
196+
_pointerType = RNGestureHandlerMouse;
176197
}
177198

178199
return self;

packages/react-native-gesture-handler/apple/Handlers/RNLongPressHandler.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ - (CGPoint)translationInView
8787

8888
- (void)touchesBegan:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
8989
{
90-
[_gestureHandler setCurrentPointerType:event];
90+
[_gestureHandler setCurrentPointerTypeForEvent:event];
9191
[super touchesBegan:touches withEvent:event];
9292

9393
if (self.state == UIGestureRecognizerStatePossible && ![self.delegate gestureRecognizerShouldBegin:self]) {

packages/react-native-gesture-handler/apple/Handlers/RNManualHandler.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ - (void)interactionsEnded:(NSSet *)touches withEvent:(UIEvent *)event
6464
#if !TARGET_OS_OSX
6565
- (void)touchesBegan:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
6666
{
67-
[_gestureHandler setCurrentPointerType:event];
67+
[_gestureHandler setCurrentPointerTypeForEvent:event];
6868
[super touchesBegan:touches withEvent:event];
6969

7070
[self interactionsBegan:touches withEvent:event];

packages/react-native-gesture-handler/apple/Handlers/RNNativeViewHandler.mm

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ - (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler
3333
#if !TARGET_OS_OSX
3434
- (void)touchesBegan:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
3535
{
36-
[_gestureHandler setCurrentPointerType:event];
36+
[_gestureHandler setCurrentPointerTypeForEvent:event];
3737
[_gestureHandler.pointerTracker touchesBegan:touches withEvent:event];
3838
}
3939

@@ -170,7 +170,7 @@ - (void)unbindFromView
170170

171171
- (void)handleTouchDown:(UIView *)sender forEvent:(UIEvent *)event
172172
{
173-
[self setCurrentPointerType:event];
173+
[self setCurrentPointerTypeForEvent:event];
174174
[self reset];
175175

176176
if (_disallowInterruption) {

packages/react-native-gesture-handler/apple/Handlers/RNPanHandler.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ - (void)mouseUp:(NSEvent *)event
238238

239239
- (void)touchesBegan:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
240240
{
241-
[_gestureHandler setCurrentPointerType:event];
241+
[_gestureHandler setCurrentPointerTypeForEvent:event];
242242
// super call was moved to interactionsBegan method to keep the
243243
// original order of calls
244244
[self interactionsBegan:touches withEvent:event];

packages/react-native-gesture-handler/apple/Handlers/RNPinchHandler.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ - (void)magnifyWithEvent:(NSEvent *)event
109109
#else
110110
- (void)touchesBegan:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
111111
{
112-
[_gestureHandler setCurrentPointerType:event];
112+
[_gestureHandler setCurrentPointerTypeForEvent:event];
113113
[super touchesBegan:touches withEvent:event];
114114
[self interactionsBegan:touches withEvent:event];
115115
}

packages/react-native-gesture-handler/apple/Handlers/RNRotationHandler.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ - (void)rotateWithEvent:(NSEvent *)event
102102
#else
103103
- (void)touchesBegan:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
104104
{
105-
[_gestureHandler setCurrentPointerType:event];
105+
[_gestureHandler setCurrentPointerTypeForEvent:event];
106106
[super touchesBegan:touches withEvent:event];
107107
[self interactionsBegan:touches withEvent:event];
108108
}

packages/react-native-gesture-handler/apple/Handlers/RNTapHandler.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ - (void)rightMouseUp:(NSEvent *)event
191191

192192
- (void)touchesBegan:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
193193
{
194-
[_gestureHandler setCurrentPointerType:event];
194+
[_gestureHandler setCurrentPointerTypeForEvent:event];
195195
[super touchesBegan:touches withEvent:event];
196196
[self interactionsBegan:touches withEvent:event];
197197
}

0 commit comments

Comments
 (0)