Skip to content

Commit 34f5e48

Browse files
authored
[iOS] Add hover callbacks to Touchable (#4397)
## Description The Apple side of `onHoverIn`/`onHoverOut`, covering iOS, tvOS and macOS. Mirrors the Android state machine, with the pointer sampled from `UIHoverGestureRecognizer` on iOS, the `NSTrackingArea` on macOS, and the touch stream during a press. - `RNGHButtonPressEventDelegate` becomes `RNGHButtonEventDelegate` now that it carries more than presses. - The touch-derived path is gated on a press-start latch. Without it a plain pencil tap on an iPad that doesn't report pencil hover would open a hover on touch-up that nothing could ever close. - The pointer type is latched for the duration of a hover — a pencil's `zOffset` collapses to zero as it approaches contact, which would otherwise make the hover-out claim a different type than the hover-in. - tvOS drives hover from focus rather than a pointer, so there's no sample to report; the payload falls back to the button's centre. ## Test plan Not covered by the JS tests. Needs an iPad with a trackpad or a hover-capable pencil, a Mac, and a tvOS device for the focus path. <details> <summary>Example code</summary> ```tsx import React, { useState } from 'react'; import { Pressable, StyleSheet, Text, View } from 'react-native'; import { GestureHandlerRootView, Touchable, } from 'react-native-gesture-handler'; export default function Example() { const [log, setLog] = useState<string[]>([]); const callbacks = (source: string) => ({ onHoverIn: () => setLog((l) => [`${source} onHoverIn`, ...l]), onHoverOut: () => setLog((l) => [`${source} onHoverOut`, ...l]), onPressIn: () => setLog((l) => [`${source} onPressIn`, ...l]), onPressOut: () => setLog((l) => [`${source} onPressOut`, ...l]), }); return ( <GestureHandlerRootView style={styles.container}> <View style={styles.row}> <Touchable style={styles.box} {...callbacks('Touchable')}> <Text style={styles.text}>Touchable</Text> </Touchable> <Pressable style={styles.box} {...callbacks('Pressable')}> <Text style={styles.text}>Pressable</Text> </Pressable> </View> {log.slice(0, 12).map((entry, i) => ( <Text key={i}>{entry}</Text> ))} </GestureHandlerRootView> ); } const styles = StyleSheet.create({ container: { flex: 1, padding: 24 }, row: { flexDirection: 'row', gap: 24, marginBottom: 24 }, box: { width: 120, height: 120, alignItems: 'center', justifyContent: 'center', backgroundColor: '#6941C6', }, text: { color: 'white' }, }); ``` </details>
1 parent 78cec50 commit 34f5e48

3 files changed

Lines changed: 221 additions & 21 deletions

File tree

‎packages/react-native-gesture-handler/apple/RNGestureHandlerButton.h‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,16 @@ typedef NS_ENUM(NSInteger, RNGHButtonEventType) {
1313
RNGHButtonEventTypePressIn,
1414
RNGHButtonEventTypePressOut,
1515
RNGHButtonEventTypeLongPress,
16+
RNGHButtonEventTypeHoverIn,
17+
RNGHButtonEventTypeHoverOut,
1618
RNGHButtonEventTypeInteractionFinished,
1719
};
1820

1921
/*
20-
* Receives press events produced by the button's state machine so that they can
21-
* be dispatched through the component's event emitter.
22+
* Receives interaction events produced by the button so that they can be
23+
* dispatched through the component's event emitter.
2224
*/
23-
@protocol RNGHButtonPressEventDelegate <NSObject>
25+
@protocol RNGHButtonEventDelegate <NSObject>
2426

2527
- (void)dispatchButtonEvent:(RNGHButtonEventType)type
2628
withExtraData:(nullable RNGestureHandlerEventExtraData *)extraData;
@@ -70,7 +72,7 @@ typedef NS_ENUM(NSInteger, RNGHButtonEventType) {
7072
*/
7173
@property (nonatomic, strong, nullable) NSNumber *managedHandlerTag;
7274

73-
@property (nonatomic, weak, nullable) id<RNGHButtonPressEventDelegate> pressEventDelegate;
75+
@property (nonatomic, weak, nullable) id<RNGHButtonEventDelegate> eventDelegate;
7476

7577
/**
7678
* The view that press animations are applied to. Defaults to self; set by the

0 commit comments

Comments
 (0)