react-native-maps: "Follow Me Button": Map stutters slowly when using onPanDrag + scrollEnabled={false} on iOS

I am trying to implement a simple “follow me” switch (like Google Maps). When the switch is ON, the map region is automatically updated every couple of seconds and follows the user. But when the user manually dose any drag gesture on the map, the switch will go to OFF mode and the map will not follow the user.

I used onPanDrag to know when the user manually moves the map. On android it’s working fine:

<MapView 
            region={this.state.region}
            onPanDrag={(e) => { this.setState({isFollowingUser:false}) }}
            onRegionChange={(region) => { this.setState({region}) }}>

The code above works great: When the user drags the map, I set isFollowingUser to FALSE. Later in the geoLocationWatchPosition callback I see that isFollowingUser equals FALSE, and do not update the region to the new location.

But on iOS , I must set scrollEnabled:false in order make onPanDrag work. But now, because scrollEnabled is FALSE, the map won’t move when the user interacts with it. So I tried to do this inside the onPanDrag:

onPanDrag(e) {
    const coord = e.nativeEvent.coordinate;
    const newRegion = {
      latitude: coord.latitude,
      longitude: coord.longitude,
      latitudeDelta: LATITUDE_DELTA,
      longitudeDelta: LONGITUDE_DELTA
    };
      this.setState({
        region: newRegion
      });
  }

The map moves, but it is barley usable. It’s very slow and stutters. I think it is related to the fact that setState is called many times a second, but how can I do it otherwise?

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 9
  • Comments: 21 (1 by maintainers)

Most upvoted comments

for me, instead of using onPanDrag, I used onMoveShouldSetResponder.

<MapView
              provider={PROVIDER_GOOGLE}
              showsUserLocation
              style={styles.mapContainer}
              showsMyLocationButton={false}
              initialRegion={this.region}
              loadingEnabled
              onUserLocationChange={(event) => followUserLocation && this.userLocationChanged(event)}
              onRegionChange={this.regionChanged}
              followUserLocation={followUserLocation}
              ref={ref => this.map = ref}
              onMoveShouldSetResponder={this.onDrag}
              key="mapa"
            >

My methods:

  userLocationChanged(event) {
    const newRegion = event.nativeEvent.coordinate;

    this.region = {
      ...this.region,
      latitude: newRegion.latitude,
      longitude: newRegion.longitude
    };

    this.animateToRegion();
  }

  animateToRegion() {
    if(this.map) {
      this.map.animateToRegion({latitude: this.region.latitude, longitude: this.region.longitude,
        latitudeDelta: this.region.latitudeDelta, longitudeDelta: this.region.longitudeDelta}, 1000);
    }
  }

  regionChanged(event) {
    this.region = {
        longitudeDelta: event.longitudeDelta,
        latitudeDelta: event.latitudeDelta,
        latitude: event.latitude,
        longitude: event.longitude
      }
  }

  onDrag(event) {
    this.setState({
      followUserLocation: false
    });
  }

My state:

  this.state = {
        erro: false,
        hasPermission: true,
        loading: true,
        followUserLocation: true
    };

I found out that storing region on state doesnt work well.

I hope this helps someone

@outaTiME you will need to disable gestures in React-Navigation

@salah-ghanim i disable gestures and no luck probably something else =(

When using react-navigation v3 onMoveShouldSetResponder stop working, any advice with that? thanks !!!

How come this is still not addressed, I still have this problem. It’s almost been a year.

@tianhaoz95 I’ve tried your solution but PID still has some margin of errors, which is not ideal until we have figured out the precise formula. I have then implemented my own solution. It works exactly as I want.

Let the component know users are interacting with the map, inject this prop into the Map component. onMoveShouldSetResponder={this.onMapDrag}

where this.onMapDrag dispatch an action to update the store (watching: false)

Instead of using onPanDrag that updates hundreds of times (it’s worse if you do this to state), we can simply get the region from onRegionChange prop, and pass it to a function.

onRegionChange = (region) => {
    let storeLatDelta = this.props.api.latestRegion.latitudeDelta
    let currentDelta = region.latitudeDelta
    let diff = Math.abs(currentDelta - storeLatDelta)
  
    // detect if user zoom or pinch the map
    if (diff > 0.0002 && diff < 0.04) {  // edit the range to your liking
      this.props.userChangeRegion()
    }
  }

Then have your componentWillReceiveProps do this

componentWillReceiveProps(nextProps) {
    // avoid moving to center during first time, coz its going back to center
    if (!(nextProps.api.watching === true && this.props.api.watching === false)) {
      if (!this.props.api.watching) {
        this.moveUserCenter()
      } 
    }
  }

Programmatically update the map, moveUserCenter = () => this.map.animateToRegion(this.props.api.latestRegion, 200);

And yeah, since we are programmatically updating the map, followUserLocation can safely set to false, and scrollEnabled to true.

@yaronlevi To implement the follow user method, just have your switch button subscribe to this, hope it helps.

centerToUser = () => {
    if (this.props.api.watching) {
      this.props.centerToUser()
      this.map.animateToRegion(this.props.api.latestRegion, 500)
    }
  }

@outaTiME I’m having a similar problem. If I use a drawer navigator or a stack navigator, which both respond to gestures, then my onMoveShouldSetResponder on the MapView is never invoked–even if I’ve disabled the navigator gestures. I think it might be down inside react-native-gesture-handler, which react-navigation now uses. There are a couple of open issues in react-native-gesture-handler that might be related, such as this one: 465 - Bug with react-native-maps. My workaround for now has been to use onStartShouldSetResponder instead; however, this is not ideal as it fires whenever the user touches the map–not just when they move it.

Having the same problem. Any solutions on this?