Repository navigation
Commit 186eb6b
authored
[iOS] Fix
## Description
On iOS a handler with `manualActivation: true` activates on its own if
its config is updated mid-gesture. Any re-render during the gesture
triggers that, since the config is re-sent on every render.
`setConfig:` runs `resetConfig` before `updateConfig:`, and
`resetConfig` went through the `manualActivation` setter, which removed
the `RNManualActivationRecognizer` from the view. `UIKit` cancels a
recognizer removed mid-touch, so the failure requirement it imposed on
the handler was lost and the new blocker created by `updateConfig:`
never saw the touch.
`resetConfig` now only clears the flag and the blocker is reconciled
once after the config is applied, only when its presence no longer
matches the flag. Android and web already reset values only.
## Test plan
<details>
<summary>Repro</summary>
```tsx
import React, { useRef, useState } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import {
GestureDetector,
GestureStateManager,
usePanGesture,
} from 'react-native-gesture-handler';
const ACTIVATION_THRESHOLD = 20;
type Phase = 'idle' | 'began' | 'active';
const color = (phase: Phase) =>
phase === 'active' ? '#21a37c' : phase === 'began' ? '#f6b914' : '#9b59b6';
export default function EmptyExample() {
const [phaseA, setPhaseA] = useState<Phase>('idle');
const [phaseB, setPhaseB] = useState<Phase>('idle');
const neverActivatePan = usePanGesture({
manualActivation: true,
runOnJS: true,
onBegin: () => {
console.log('[A never-activate] onBegin');
setPhaseA('began');
},
onActivate: () => {
console.log('[A never-activate] onActivate (SHOULD NOT HAPPEN)');
setPhaseA('active');
},
onFinalize: (e) => {
console.log(`[A never-activate] onFinalize canceled=${e.canceled}`);
setPhaseA('idle');
},
});
const start = useRef({ x: 0, y: 0 });
const requested = useRef(false);
const selfActivatePan = usePanGesture({
manualActivation: true,
runOnJS: true,
onTouchesDown: (e) => {
const t = e.allTouches[0];
start.current = { x: t.x, y: t.y };
requested.current = false;
},
onTouchesMove: (e) => {
if (requested.current) {
return;
}
const t = e.allTouches[0];
const dx = t.x - start.current.x;
const dy = t.y - start.current.y;
if (Math.hypot(dx, dy) > ACTIVATION_THRESHOLD) {
requested.current = true;
GestureStateManager.activate(e.handlerTag);
}
},
onBegin: () => {
console.log('[B self-activate] onBegin');
setPhaseB('began');
},
onActivate: () => {
console.log('[B self-activate] onActivate');
setPhaseB('active');
},
onFinalize: (e) => {
console.log(`[B self-activate] onFinalize canceled=${e.canceled}`);
setPhaseB('idle');
},
});
return (
<View style={styles.container}>
<Text style={styles.label}>A: manualActivation, never activated</Text>
<GestureDetector gesture={neverActivatePan}>
<View style={[styles.box, { backgroundColor: color(phaseA) }]} />
</GestureDetector>
<Text style={styles.label}>
B: manualActivation, activate() after {ACTIVATION_THRESHOLD}px
</Text>
<GestureDetector gesture={selfActivatePan}>
<View style={[styles.box, { backgroundColor: color(phaseB) }]} />
</GestureDetector>
<Text style={styles.hint}>
Purple = idle, orange = BEGAN, green = ACTIVE. Drag each box. A must
never turn green.
</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
gap: 8,
},
label: { marginTop: 16, fontSize: 15, opacity: 0.6 },
hint: { marginTop: 24, fontSize: 13, opacity: 0.5, textAlign: 'center' },
box: { width: 150, height: 150, borderRadius: 12 },
});
```
</details>manualActivation being ignored after a config update mid-gesture (#4520)1 parent 2f688d8 commit 186eb6b
1 file changed
Lines changed: 14 additions & 3 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
116 | 116 | | |
117 | 117 | | |
118 | 118 | | |
119 | | - | |
| 119 | + | |
120 | 120 | | |
121 | 121 | | |
122 | 122 | | |
| |||
132 | 132 | | |
133 | 133 | | |
134 | 134 | | |
| 135 | + | |
135 | 136 | | |
136 | 137 | | |
137 | 138 | | |
| |||
665 | 666 | | |
666 | 667 | | |
667 | 668 | | |
| 669 | + | |
| 670 | + | |
| 671 | + | |
| 672 | + | |
| 673 | + | |
| 674 | + | |
668 | 675 | | |
669 | | - | |
| 676 | + | |
| 677 | + | |
| 678 | + | |
| 679 | + | |
| 680 | + | |
670 | 681 | | |
671 | 682 | | |
672 | 683 | | |
673 | 684 | | |
674 | 685 | | |
675 | | - | |
| 686 | + | |
676 | 687 | | |
677 | 688 | | |
678 | 689 | | |
| |||
0 commit comments