software-mansion / react-native-svg

SVG library for React Native, React Native Web, and plain React web projects.
MIT License
7.44k stars 1.12k forks source link

No pan responder events fired from SVG elements inside a Modal on Android #473

Closed psivanov closed 5 years ago

psivanov commented 6 years ago

react-native-svg@6.0.0-rc1 react-native@0.48.3 react@16.0.0-alpha.12

I have a responder on individual (nested) children (<G>s) of an node. This stopped working on Android. No events get fired (or at least none of the handlers gets called).

This is how I used to be able to make it work:

    componentWillMount() {
        console.log('WILL MOUNT');
        this.panResponder = PanResponder.create({
            onStartShouldSetPanResponder: this.handleStartShouldSetPanResponder,
            onMoveShouldSetPanResponder: this.handleMoveShouldSetPanResponder,
            onPanResponderGrant: this.handlePanResponderGrant,
            onPanResponderMove: this.handlePanResponderMove,
            onPanResponderRelease: this.handlePanResponderEnd,
            onPanResponderTerminate: this.handlePanResponderEnd,
            onPanResponderTerminationRequest: this.handlePanResponderTerminationRequest
        });
    }
...
    render() {
        return (
        <SVG>
            <G
                {...this.panResponder.panHandlers}
            >
            ...
            </G>
        </SVG>
        );
    }
psivanov commented 6 years ago

What is the status of this issue? I tried react-native-svg@6.0.1-rc2 and it's still broken for Android only (works perfectly on iOS).

byebrianwong commented 6 years ago

I'm having the same issue as well, wondering if there are any updates to this?

msand commented 6 years ago

Do you have any fill/stroke for the touch interaction to intersect with? Otherwise it won't make the hitTest succeed.

msand commented 6 years ago

Or can you post a complete reproduction?

msand commented 6 years ago

Alternatively you can wrap it in a View and set the panResponder on that, e.g. like this: https://github.com/msand/zoomable-svg/blob/master/index.js#L281

psivanov commented 6 years ago

In my case, I have stroke only not fill, but it works totally fine on iOS. It's just Android that it doesn't work on.

The pan responder works when set on the <SVG> element. But this is not a good solution in my case, because I want to detect touches on its children (which are rectanglular boxes with handles scattered somewhere on the canvas). If I were to use a global handler for the whole <SVG> then I need to basically implement hit testing myself, so that defeats the purpose. Unless you mean to do something like this (which I thought won't work, so I never tried)?

<SVG>
   <View>
      <G {...this.panResponder.panHandlers}>
         //my rectangle with handles etc.
      </G>
  </View>
  ...
<SVG>

I debugged a little and I see the event firing, but the _dispatchListeners property of the target of the event is null in setResponderAndExtractTransfer. On iOS it is not null. So the event handlers are for some reason not attached correctly on Android, but I don't understand well enough where this is supposed to happen.

psivanov commented 6 years ago

Here is the complete reproduction:

import { GestureResponderEvent, PanResponder, PanResponderInstance, StyleSheet, View } from 'react-native'
import { Rect, Svg } from 'react-native-svg';

export interface DesignSpacecontainerProps {
    maxDim: number
}

export class DesignSpaceContainer extends React.Component<DesignSpacecontainerProps, {}> {

    private panResponder: PanResponderInstance;

    componentWillMount() {
        console.log('WILL MOUNT: design space');
        this.panResponder = PanResponder.create({
            onStartShouldSetPanResponder: this.handleStartShouldSetPanResponder,
            onMoveShouldSetPanResponder: this.handleMoveShouldSetPanResponder,
            onPanResponderGrant: this.handlePanResponderGrant,
            onPanResponderMove: this.handlePanResponderMove,
            onPanResponderRelease: this.handlePanResponderEnd,
            onPanResponderTerminate: this.handlePanResponderEnd
        });
    }

    render() {
        const { maxDim } = this.props;

        return (
            <View style={styles.root}>
                <Svg height={maxDim} width={maxDim} style={styles.canvas}>
                    <Rect
                        fill="#ff0000"
                        fillOpacity={1}
                        height={maxDim * 0.5}
                        strokeOpacity={1}
                        width={maxDim * 0.5}
                        x={maxDim * 0.25}
                        y={maxDim * 0.25}
                        {...this.panResponder.panHandlers}
                    />
                </Svg>
            </View>
        )
    }

    //<handle pan responder>
    private handleStartShouldSetPanResponder = (e: GestureResponderEvent) => {
        console.log('start set pan responder: design space');
        return true;
    }
    private handleMoveShouldSetPanResponder = (e: GestureResponderEvent) => {
        console.log('move set pan responder: design space');
        return false;
    }
    private handlePanResponderGrant = (e: GestureResponderEvent) => {
        console.log('grant: design space');
    }

    private handlePanResponderMove = (e: GestureResponderEvent) => {
        console.log('move: design space');
    }

    private handlePanResponderEnd = (e: GestureResponderEvent) => {
        console.log('end: design space');
    }
    //</handle pan responder>
}

const styles = StyleSheet.create({
    root: {
        flex: 1
    } as StyleSheet.Style,
    canvas: {
        position: 'absolute',
        left: 0,
        top: 0
    } as StyleSheet.Style
});

Load and then touch and drag within the red square:

iOS: DesignSpaceContainer.js:28 WILL MOUNT: design space DesignSpaceContainer.js:9 start set pan responder: design space DesignSpaceContainer.js:17 grant: design space 7DesignSpaceContainer.js:20 move: design space DesignSpaceContainer.js:23 end: design space

Android: DesignSpaceContainer.js:28 WILL MOUNT: design space

psivanov commented 6 years ago

@msand,

I discovered something else, which might help fixing this issue. My <DesignSpaceContainer> component (code above) is actually inside another component, which is inside a <Modal>. So it boils down to something like this:

<Modal 
    visible={isDesignToolVisible}
    animationType={'slide'}
    onRequestClose={productPageStore.onDesignToolCancel}
>
        ...
    <View style={{ height: deviceWidth, width: deviceWidth }} onLayout={this.handleDesignSpaceLayout} >
        <DesignSpaceContainer
            absolutePosition={this.state.designSpaceAbsolutePosition}
            maxDim={deviceWidth}
        />
    </View>
        ...
</Modal>

So the target element of the synthetic event, which is fired is the <Modal>, not the SVG <Rect> element. Removing the <Modal> wrapper fixes the touch event handling, but I really do need it to be in a modal...

So at the end this is a <Modal> incompatibility issue specific to Android.

I hope this is easily fixable or maybe someone can suggest a workaround.

psivanov commented 6 years ago

Same issue: #264

msand commented 6 years ago

@psivanov I think I might have fixed the issue now, can you try with the latest commit from the master branch and this code:

import React, { Component } from 'react';
import { PanResponder, StyleSheet, View } from 'react-native';
import { Rect, Svg } from 'react-native-svg';

export default class DesignSpaceContainer extends Component {
  componentWillMount() {
    console.log('WILL MOUNT: design space');
    this.panResponder = PanResponder.create({
      onStartShouldSetPanResponder: this.handleStartShouldSetPanResponder,
      onMoveShouldSetPanResponder: this.handleMoveShouldSetPanResponder,
      onPanResponderGrant: this.handlePanResponderGrant,
      onPanResponderMove: this.handlePanResponderMove,
      onPanResponderRelease: this.handlePanResponderEnd,
      onPanResponderTerminate: this.handlePanResponderEnd,
    });
  }

  render() {
    return (
      <View style={styles.root}>
        <Svg height="100%" width="100%" viewBox="0 0 100 100">
          <Rect
            {...this.panResponder.panHandlers}
            strokeOpacity="1"
            fillOpacity="1"
            fill="#ff0000"
            height="50"
            width="50"
            x="25"
            y="25"
          />
        </Svg>
      </View>
    );
  }

  handleStartShouldSetPanResponder = e => {
    console.log('start set pan responder: design space');
    return true;
  };
  handleMoveShouldSetPanResponder = e => {
    console.log('move set pan responder: design space');
    return false;
  };
  handlePanResponderGrant = e => {
    console.log('grant: design space');
  };

  handlePanResponderMove = e => {
    console.log('move: design space');
  };

  handlePanResponderEnd = e => {
    console.log('end: design space');
  };
}

const styles = StyleSheet.create({
  root: {
    flex: 1,
  }
});
claudioviola commented 5 years ago

Same issue here. I fixed but looks like a workaround...

I tried to add panResponder. panHandlers to SVG parent node and it works (I mean not to the child Rect as in the previous example)... but honestly I don't know if there is something of counterproductive...

msand commented 5 years ago

Is this still an issue? Can you provide a full reproduction?

msand commented 5 years ago

This seems to work correctly in the latest version, please comment if that's not the case.

Dheeeraj commented 4 years ago

Same issue here. I fixed but looks like a workaround...

I tried to add panResponder. panHandlers to SVG parent node and it works (I mean not to the child Rect as in the previous example)... but honestly I don't know if there is something of counterproductive...

same problem panResponder is not working for child G inside a modal of android. i am using "react-native-svg": "^9.13.6"

msand commented 4 years ago

Can you make a reproduction using the latest version?

akshitsharma commented 3 years ago

same problem panResponder is not working for child G inside a modal of android. i am using "react-native-svg": "^9.13.6"

same here