Skip to content

Comments

[ios] fix hit frame#3906

Closed
akwasniewski wants to merge 3 commits intonextfrom
@akwasniewski/fix-ios-hitFrame
Closed

[ios] fix hit frame#3906
akwasniewski wants to merge 3 commits intonextfrom
@akwasniewski/fix-ios-hitFrame

Conversation

@akwasniewski
Copy link
Contributor

Description

The minDistance property did not work on ios. The reason was that the GestureDetector view is sometimes getting collapsed so that it bounds are {x: 0, y:0, width:0, height:0}, then the function isWithinBounds will always return false. The solution is to check whether the view is collapsed, and if so, get the bounds of its subview instead.

Test plan

Tested on the following example, minDistance works!

Details
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import {
  GestureDetector,
  GestureHandlerRootView,
  useLongPressGesture,
  usePanGesture,
  useSimultaneousGestures,
} from 'react-native-gesture-handler';
import Animated, {
  useSharedValue,
  useAnimatedStyle,
  interpolateColor,
  withTiming,
} from 'react-native-reanimated';

export default function PanExample() {
  const translateX = useSharedValue(0);
  const translateY = useSharedValue(0);
  const colorProgress = useSharedValue(0);
  const offsetX = useSharedValue(0);
  const offsetY = useSharedValue(0);

  const panGesture = usePanGesture({
    onBegin: () => {
      'worklet';
      colorProgress.value = withTiming(1, { duration: 150 });
    },
    onUpdate: (event) => {
      'worklet';
      translateX.value = offsetX.value + event.translationX;
      translateY.value = offsetY.value + event.translationY;
    },
    onFinalize: () => {
      'worklet';
      offsetX.value = translateX.value;
      offsetY.value = translateY.value;
    },
  });

  const longPressGesture = useLongPressGesture({
    onBegin: () => {
      'worklet';
      colorProgress.value = withTiming(1, {
        duration: 100,
      });
    },
    onActivate: () => {
      'worklet';
      colorProgress.value = withTiming(2, {
        duration: 100,
      });
    },
    onFinalize: () => {
      'worklet';
      colorProgress.value = withTiming(0, {
        duration: 100,
      });
    },
    minDuration: 1000,
    maxDistance: 10000000000000,
  });

  const gestures = useSimultaneousGestures(longPressGesture, panGesture);
  const animatedStyle = useAnimatedStyle(() => {
    const backgroundColor = interpolateColor(
      colorProgress.value,
      [0, 1, 2],
      ["navy", "purple", "blue"]
    );
    return {
      transform: [
        { translateX: translateX.value },
        { translateY: translateY.value },
      ],
      backgroundColor,
    };
  });

  return (
    <GestureHandlerRootView>
      <View style={styles.centerView}>
        <View>
          <GestureDetector gesture={gestures}>
            <Animated.View style={[styles.box, animatedStyle]} />
          </GestureDetector>
        </View>
      </View>
    </GestureHandlerRootView>
  );
}

const styles = StyleSheet.create({
  centerView: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  box: {
    height: 120,
    width: 120,
    borderRadius: 60,
    marginBottom: 100,
  },
  instructions: {
    marginTop: 8,
    textAlign: 'center',
    paddingHorizontal: 16,
  },
});

@akwasniewski
Copy link
Contributor Author

I'm not a big fan of this solution, I think it may introduce other bugs, but after a lot of tinkering around this proved to be the only working solution.

@akwasniewski akwasniewski mentioned this pull request Jan 9, 2026
@j-piasecki
Copy link
Member

Hmm, weird that the detector has the wrong dimensions while its children are sized correctly. Could you check this part to see if the values there seem correct?

@j-piasecki
Copy link
Member

Can you change if the issue is still there with #3930 applied?

@akwasniewski
Copy link
Contributor Author

Can you change if the issue is still there with #3930 applied?

I'm surprised to report that fixing shadow node problem did not help. It is still broken without the fix in this PR.

@akwasniewski
Copy link
Contributor Author

Superseded by #3942

j-piasecki added a commit that referenced this pull request Feb 2, 2026
## Description

Addresses the underlying issue of
#3906

The original issue described in the above PR was caused by wrong shadow
node dimensions, which were fixed by
#3930.
After that, the dimensions were good, but the long press was still
failing. This was caused by `shouldCancelWhenOutside` checking the
dimensions of the detector while the child was moved.

This PR changes the logic so that the child's hitbox is checked when
using the native detector. This should be enough for iOS, but on
Android, further investigation is needed into whether the entire
`transformedEvent` should be in the coordinate space of the detector or
its child.

## Test plan

See
#3906
test plan
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.

2 participants