React-native: ScrollView inside Pan Responder not scrolling

Created on 30 Nov 2016  路  17Comments  路  Source: facebook/react-native

Description

When a ScrollView is a child of a View with a PanResponder set, the ScrollView (usually) does not scroll. I looked into it further and it seems that even if you give it a PanResponder, it never receives the grant.

For example:

export default class scrollPan extends Component {
  constructor(props){
    super(props);
    this.wrapperPanResponder = PanResponder.create({
      onStartShouldSetPanResponder: (e, g) => true,
      onPanResponderGrant: () => {
        console.log('GRANTED TO WRAPPER');
      },
      onPanResponderMove: (evt, gestureState) => {
        console.log('WRAPPER MOVED');
      }
    });

    this.scollerPanResponder = PanResponder.create({
      onStartShouldSetPanResponder: (e, g) => true,
      onPanResponderGrant: () => {
        console.log('GRANTED TO SCROLLER');
      },
      onPanResponderMove: (evt, gestureState) => {
        console.log('SCROLLER MOVED');
      }
    });
  }
  render() {
    return (
      <View style={styles.container} {...this.wrapperPanResponder.panHandlers}>
        <ScrollView onScroll={() => console.log('scrolled')} style={{maxHeight: 350}} {...this.scollerPanResponder.panHandlers}>
          <Text style={{fontSize:96}}>Scroll this</Text>
          <Text style={{fontSize:96}}>Scroll this</Text>
          <Text style={{fontSize:96}}>Scroll this</Text>
          <Text style={{fontSize:96}}>Scroll this</Text>
          <Text style={{fontSize:96}}>Scroll this</Text>
        </ScrollView>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
});

When attempting to scroll, this logs GRANTED TO WRAPPER and WRAPPER_MOVED (the same is true if you remove scrollerPanResponder). However, if you change wrapperPanResponder's onStartShouldSetPanResponder to return false like so:

    this.wrapperPanResponder = PanResponder.create({
      onStartShouldSetPanResponder: (e, g) => false,
      onPanResponderGrant: () => {
        console.log('GRANTED TO WRAPPER');
      },
      onPanResponderMove: (evt, gestureState) => {
        console.log('WRAPPER MOVED');
      }
    });

it scrolls and logs GRANTED TO SCROLLER, SCROLLER_MOVED, and also scrolled which, to my understanding, is correct. Additionally, if you replace the ScrollView with a View, scrollerPanResponder gets the grant, so the issue seems to be specific to ScrollViews whose parents have a panResponder.

onStartShouldSetPanResponderCapture and onMoveShouldSetPanResponderCapture don't seem to make a difference.

Reproduction

I couldn't get rnplay to work, but I do have a sample app here.

Solution

I'm not sure where to begin with a pull request, but ideally pans started within the ScrollView should be handled by the ScrollView. If that's not desireable by default, some means of enabling it would be nice to have.

Additional Information

  • React Native version: 0.38.0
  • Platform: iOS and Android
  • Operating System: MacOS

I have seen lots of bugs for the reverse situation (pan responders inside scrollviews), which didn't seem to address this. I saw 6764 but his workaround appears to be specific to his use case, and I'm not sure even that would work here.

Hopefully I'm just doing something wrong, if I figure that out or find a workaround I'll post it.

Stale

Most helpful comment

@brendan-rius Sorry to hear you're encountering this. I agree that the issue should be addressed, I only posted my workaround to help people who were in the same situation as me.

All 17 comments

I've found a workaround for my use case. If you put the panResponder on a sibling of the ScrollView (or its ancestor) the ScrollView behaves properly. You can then absolutely position that sibling so that it's laid out similarly to the parent. After that, things seem to work as expected. So my example would look like this:

export default class scrollPan extends Component {
  constructor(props){
    super(props);
    this.wrapperPanResponder = PanResponder.create({
      onStartShouldSetPanResponder: (e, g) => true,
      onPanResponderGrant: () => {
        console.log('GRANTED TO WRAPPER');
      },
      onPanResponderMove: (evt, gestureState) => {
        console.log('WRAPPER MOVED');
      }
    });

    this.scollerPanResponder = PanResponder.create({
      onStartShouldSetPanResponder: (e, g) => true,
      onPanResponderGrant: () => {
        console.log('GRANTED TO SCROLLER');
      },
      onPanResponderMove: (evt, gestureState) => {
        console.log('SCROLLER MOVED');
      }
    });
  }
  render() {
    return (
      <View style={styles.container}>
        <View style={styles.pan_container} {...this.wrapperPanResponder.panHandlers} />
        <ScrollView onScroll={() => console.log('scrolled')} style={styles.scroll_view} {...this.scollerPanResponder.panHandlers}>
          <Text style={{fontSize:96}}>Scroll this</Text>
          <Text style={{fontSize:96}}>Scroll this</Text>
          <Text style={{fontSize:96}}>Scroll this</Text>
          <Text style={{fontSize:96}}>Scroll this</Text>
          <Text style={{fontSize:96}}>Scroll this</Text>
        </ScrollView>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  pan_container: {
    ...StyleSheet.absoluteFillObject,
    backgroundColor: 'blue'
  },
  scroll_view: {
    backgroundColor: 'teal',
    maxHeight: 350
  }
});

I had to cover the entire page, but this solution should be adaptable to other layouts.

An example of this workaround can be found here.

@liamfd I have exactly the same problem on 0.41 on iOS (did not try an Android). This is clearly a problem here that needs to be solved. Your solution is a hack not easily applicable everywhere and should not stop the issue from being fixed

@brendan-rius Sorry to hear you're encountering this. I agree that the issue should be addressed, I only posted my workaround to help people who were in the same situation as me.

Any updates on this?

Exactly same need. Any update?

@liamfd any update?

updates?

@liamfd just re-read my comment and it sounds a bit aggressive. Did not mean to insult your work :) thank you for helping

really need

@liamfd updates?

For those who are interested, here is a very dirty hack:

class PatchedScrollView extends React.PureComponent {
    componentDidMount() {
        // Dirty hack
        this._scrollView.scrollResponderHandleStartShouldSetResponder = () => true
    }

    render() {
        return (
            <ScrollView ref={x => this._scrollView = x} {...this.props}>
                {this.props.children}
            </ScrollView>
        )
    }
}

You can use it like a normal scroll view

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Maybe the issue has been fixed in a recent release, or perhaps it is not affecting a lot of people. If you think this issue should definitely remain open, please let us know why. Thank you for your contributions.

I'm going to bump this as well, I've been searching for a few hours and haven't found an updated solution to this problem.

I have the same problem.

@arronhunt you can vote for a similar problem https://react-native.canny.io/feature-requests/p/scrollview-panresponder

I was able to resolve this problem by wrapping the child of the scrollview in a < TouchableWithoutFeedback> component, as mentioned in this Stack Overflow article: https://stackoverflow.com/a/41753389.

any update ?

Was this page helpful?
0 / 5 - 0 ratings