Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Programmatically bring to front a MarkerView #914

Closed
xseignard opened this issue Jun 11, 2020 · 15 comments
Closed

Programmatically bring to front a MarkerView #914

xseignard opened this issue Jun 11, 2020 · 15 comments
Labels
Needs: Author Feedback Needs: Repro This issue could be improved with a clear list of steps to reproduce the issue. wontfix This will not be worked on

Comments

@xseignard
Copy link

Describe the bug
I need to keep in sync an horizontal FlatList and it's corresponding MarkerView on a map. So a good idea would be to be able to programmatically bring to front the MarkerView when scrolling from the FlatList

I tried to add some bringToFront/bringToBack to RNMGL MarkerView successfully to android, but I'm struggling with the ios version.

So far I tried the following approaches on RCTMGLPointAnnotation and none are satisfying

// attempt 1
self.layer.zPosition = CGFLOAT_MAX;
// attempt 2
[self.superview bringSubviewToFront:self];
// attempt 3
self.layer.zPosition = CGFLOAT_MAX;
for (int i = 0; i < self.reactSubviews.count; i++) {
    self.reactSubviews[i].layer.zPosition = CGFLOAT_MAX;
}

Any idea to achieve this ?

Expected behavior
Having a possibilty to programmatically bring to front a MarkerView

Screenshots
The goal would be to avoid such situations.
24168

Versions (please complete the following information):

  • Platform: Android and iOS
  • Device: iPhone6
  • Emulator/ Simulator: no
  • OS: iOS 12.4.6
  • react-native-mapbox-gl Version ALL
  • React Native Version 0.62.2
@mfazekas
Copy link
Contributor

@xseignard thanks for the report. Can you also post a simple and single standalone component that reproduces the issue?!

@xseignard
Copy link
Author

Hi @mfazekas sure I could, but since it's more a feature request than an issue, I don't know what example I can provide.
I can open a PR with some android code but would definitely need help/guidance for the iOS part. Would that be better ?

@mfazekas
Copy link
Contributor

@xseignard as a first step i was more thinking on a JS/RN component that reproduces the issue.

Something like this:
https://github.com/react-native-mapbox-gl/maps/pull/812#issue-401860003

The problem sounds clear, but sometimes details matter so a concrete code to repro the issue would be great.

@xseignard
Copy link
Author

xseignard commented Jun 12, 2020

Re, so here is the code:

import React from 'react';
import {View, Text, SafeAreaView} from 'react-native';
import MapboxGL, {
  MapView,
  Camera,
  MarkerView,
} from '@react-native-mapbox-gl/maps';

MapboxGL.setAccessToken('XXXXXX');
MapboxGL.setTelemetryEnabled(false);

const Marker = ({coordinate, id, color, label}) => {
  return (
    <MarkerView coordinate={coordinate} id={id}>
      <View
        style={{
          width: 20,
          height: 20,
          borderRadius: 10,
          backgroundColor: color,
        }}
      />
      <View>
        <Text>{label}</Text>
      </View>
    </MarkerView>
  );
};

export default () => {
  return (
    <SafeAreaView style={{flex: 1}}>
      <MapView style={{flex: 1}}>
        <Camera
          centerCoordinate={[-1.54781, 47.2155166]}
          zoomLevel={20}
          defaultSettings={{
            centerCoordinate: [-1.54781, 47.2155166],
            zoomLevel: 20,
          }}
        />
        <Marker
          coordinate={[-1.54781, 47.2155166]}
          id="1"
          color="red"
          label="Label 1"
        />
        <Marker
          coordinate={[-1.547815, 47.2155166]}
          id="2"
          color="blue"
          label="Label 2"
        />
      </MapView>
    </SafeAreaView>
  );
};

So the 2 Marker are pretty close.
As shown below they overlap and zIndexes are random across app reload.
IMG_0001
IMG_0002

So being able to programmatically bring to front/back markers would be a nice idea (and very important in my use case :) )

@mfazekas
Copy link
Contributor

mfazekas commented Jun 13, 2020

On ios i can just use the standard way to reorder views, zIndex

import React, {useState} from 'react';
import {View, Text, SafeAreaView, Button} from 'react-native';
import MapboxGL, {
  MapView,
  Camera,
  MarkerView,
} from '@react-native-mapbox-gl/maps';


const Marker = ({coordinate, id, color, label, zIndex}) => {
  return (
    <MarkerView
      coordinate={coordinate}
      id={id}
      style={{zIndex}}>
      <View
        style={{
          width: 40,
          height: 40,
          borderRadius: 10,
          backgroundColor: color,
        }}
      />
      <View>
        <Text>{label}</Text>
      </View>
    </MarkerView>
  );
};

export default () => {
  let [ordered, setOrdered] = useState(false);

  return (
    <SafeAreaView style={{flex: 1}}>
      <Button title="reorder" onPress={() => setOrdered(!ordered) } />
      <MapView style={{flex: 1}}>
        <Camera
          centerCoordinate={[-1.54781, 47.2155166]}
          zoomLevel={20}
          defaultSettings={{
            centerCoordinate: [-1.54781, 47.2155166],
            zoomLevel: 20,
          }}
        />
        <Marker
          coordinate={[-1.54781, 47.2155166]}
          id="1"
          color="red"
          label="Label 1"
          zIndex={ordered ? 1 : 3}
        />
        <Marker
          coordinate={[-1.547815, 47.2155166]}
          id="2"
          color="blue"
          label="Label 2"
          zIndex={ordered ? 3 : 1}
        />
      </MapView>
    </SafeAreaView>
  );
};

@xseignard
Copy link
Author

xseignard commented Jun 13, 2020

It's not working, I can still reproduce the random zIndexes with the following code:

import React from 'react';
import {View, Text, SafeAreaView} from 'react-native';
import MapboxGL, {
  MapView,
  Camera,
  MarkerView,
} from '@react-native-mapbox-gl/maps';

MapboxGL.setAccessToken('XXXX');
MapboxGL.setTelemetryEnabled(false);

const Marker = ({coordinate, id, color, label, zIndex}) => {
  return (
    <MarkerView coordinate={coordinate} id={id} style={{zIndex}}>
      <View
        style={{
          width: 20,
          height: 20,
          borderRadius: 10,
          backgroundColor: color,
        }}
      />
      <View>
        <Text>{label}</Text>
      </View>
    </MarkerView>
  );
};

export default () => {
  return (
    <SafeAreaView style={{flex: 1}}>
      <MapView style={{flex: 1}}>
        <Camera
          centerCoordinate={[-1.54781, 47.2155166]}
          zoomLevel={20}
          defaultSettings={{
            centerCoordinate: [-1.54781, 47.2155166],
            zoomLevel: 20,
          }}
        />
        <Marker
          coordinate={[-1.54781, 47.2155166]}
          id="1"
          color="red"
          label="Label 1"
          zIndex={1}
        />
        <Marker
          coordinate={[-1.547815, 47.2155166]}
          id="2"
          color="blue"
          label="Label 2"
          zIndex={3}
        />
      </MapView>
    </SafeAreaView>
  );
};

Please see video https://imgur.com/gH2OjrO

@mfazekas
Copy link
Contributor

mfazekas commented Jun 13, 2020

@xseignard that is because we set zPosition on point annotations.
See
https://github.com/react-native-mapbox-gl/maps/blob/8cae0a28f784759ee5a4432c175cb3726826c63c/ios/RCTMGL/RCTMGLPointAnnotation.m#L136

If you replace it with

if (self.layer.zPosition == 0.0) {
   self.layer.zPosition = [self _getZPosition];
}

then setting zIndex on initial render should work as well

@xseignard
Copy link
Author

Thanks, this indeed works on the small exemple.
But not on my use case...
On the following video you can see that it's working ok, and stops working as soon as I apply some filter that adds or remove markers (see the marker on top of the callout at the end of the video)
https://imgur.com/a/dV79BbW

Been struggling with that for more than a month now, and completely lost on what's wrong.

@mfazekas
Copy link
Contributor

mfazekas commented Jun 14, 2020

@xseignard Note that zPosition only works between siblings. Not sure if you can have MarkerViews as nonsiblings, with RNMBGL, but probably worth a check.

You can modify the example to be more like your rw code, or simplify your rw code to be more like your example. In eithere case it's hard to see from the video what's wrong with your code or RNMBGL-s code.

By sybling i mean that this works:

<View>
   <View style={{zIndex:3}}
   <View style={{zIndex:1}}
</View>

this doesnt:

<View>
   <View>
     <View style={{zIndex:3}} />
  </View>
  <View style={{zIndex:1}} />
</View>

@mfazekas mfazekas added Needs: Author Feedback Needs: Repro This issue could be improved with a clear list of steps to reproduce the issue. labels Jun 14, 2020
@xseignard
Copy link
Author

So I tried to recreate something similar to my use case:

import React, {useState, memo} from 'react';
import {View, Text, SafeAreaView, StyleSheet, Button} from 'react-native';
import MapboxGL, {
  MapView,
  Camera,
  MarkerView,
} from '@react-native-mapbox-gl/maps';

MapboxGL.setAccessToken('XXXX');
MapboxGL.setTelemetryEnabled(false);

const fakeData = [
  {coordinate: [-1.547, 47.2139], someFilter: false, label: 'Moore'},
  {coordinate: [-1.5487, 47.2171], someFilter: false, label: 'Jerde'},
  {coordinate: [-1.5477, 47.2137], someFilter: false, label: 'Langworth'},
  {coordinate: [-1.5495, 47.2148], someFilter: true, label: 'Hirthe'},
  {coordinate: [-1.5495, 47.215], someFilter: false, label: 'McGlynn'},
  {coordinate: [-1.5476, 47.2137], someFilter: false, label: 'Hahn'},
  {coordinate: [-1.5462, 47.2148], someFilter: false, label: 'Hills'},
  {coordinate: [-1.5468, 47.214], someFilter: true, label: 'Kassulke'},
  {coordinate: [-1.5466, 47.2169], someFilter: true, label: 'Lindgren'},
  {coordinate: [-1.5486, 47.2171], someFilter: true, label: 'Parker'},
  {coordinate: [-1.5496, 47.2154], someFilter: true, label: 'Fay'},
  {coordinate: [-1.5495, 47.2162], someFilter: true, label: 'Green'},
  {coordinate: [-1.548, 47.2173], someFilter: true, label: 'Kessler'},
  {coordinate: [-1.549, 47.2142], someFilter: false, label: 'Gutkowski'},
  {coordinate: [-1.5482, 47.2173], someFilter: false, label: 'Padberg'},
  {coordinate: [-1.5495, 47.215], someFilter: false, label: 'Waters'},
  {coordinate: [-1.5464, 47.2166], someFilter: false, label: 'Aufderhar'},
  {coordinate: [-1.5461, 47.215], someFilter: false, label: 'Boyer'},
  {coordinate: [-1.5462, 47.2163], someFilter: true, label: 'Buckridge'},
  {coordinate: [-1.5463, 47.2165], someFilter: false, label: 'Turcotte'},
];

const Marker = memo(({data, id, selected}) => {
  const markerViewStyle = {
    zIndex: selected ? 3 : 1,
  };
  const markerStyle = {
    backgroundColor: data.someFilter ? 'red' : 'blue',
    opacity: selected ? 1 : 0.6,
  };

  return (
    <MarkerView
      coordinate={data.coordinate}
      id={`${id}`}
      style={[styles.markerView, markerViewStyle]}>
      <View style={[styles.marker, markerStyle]} />
      {selected && (
        <View style={styles.callout}>
          <Text>{data.label}</Text>
        </View>
      )}
    </MarkerView>
  );
});

export default () => {
  const [selectedId, setSelectedId] = useState(0);

  const handlePress = () => {
    setSelectedId((selectedId + 1) % fakeData.length);
  };

  return (
    <SafeAreaView style={styles.constainer}>
      <MapView style={styles.map}>
        <Camera
          centerCoordinate={[-1.54781, 47.2155166]}
          zoomLevel={14}
          defaultSettings={{
            centerCoordinate: [-1.54781, 47.2155166],
            zoomLevel: 14,
          }}
        />
        {fakeData.map((d, i) => (
          <Marker key={i} data={d} id={i} selected={selectedId === i} />
        ))}
      </MapView>
      <Button title="Next marker" onPress={handlePress} />
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  constainer: {
    flex: 1,
  },
  map: {
    flex: 1,
  },
  markerView: {
    position: 'absolute',
  },
  marker: {
    width: 20,
    height: 20,
    borderRadius: 10,
  },
  callout: {
    position: 'absolute',
    top: -30,
    width: 80,
    backgroundColor: 'white',
    padding: 5,
    flexDirection: 'row',
    justifyContent: 'center',
  },
});

@xseignard
Copy link
Author

Here is some result:

  • I don't know why the callouts ain't disappearing
  • but the intersting thing is that "Langworth" is the current selected marker AND below other even with a higher zIndex
    IMG_9831A3527917-1

@mfazekas
Copy link
Contributor

image

Interesting that it does render Langworth on top for me.
Which RNMBLG are you using and which Device?! Have you tried on simulator too?!

Also note that MarkerView and PointAnnotation does not support multiple child views. (You can add a Callbout too, but it should be Callout not a plain View. That's why you see the strange callout issues.

https://github.com/react-native-mapbox-gl/maps/blob/8cae0a28f784759ee5a4432c175cb3726826c63c/ios/RCTMGL/RCTMGLPointAnnotation.m#L30-L38

@stale
Copy link

stale bot commented Aug 14, 2020

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the wontfix This will not be worked on label Aug 14, 2020
@stale stale bot closed this as completed Aug 21, 2020
@anyway2019
Copy link

@xseignard can you me show the android code,i use android View.bringToFront() method in native code but not working.

@Ajmal0197
Copy link

I have fixed it using plain javascript. Just pushed the marker to last of array from anywhere in between.

    case PUSH_MAP_MARKER_ON_TOP:
      let featureCollectionNewProps = {};
      try {
        let propertiesGJ = state.geoJSONPropertiesData?.features || [];
        let payloadId = payload?.data?.properties?.id;
        console.log('PUSH_MAP_MARKER_TOP_TOP0', payloadId, propertiesGJ);

        let toBeReplaced =
          propertiesGJ.find(item => item?.properties?.id === payloadId) || {};
        propertiesGJ.splice(
          propertiesGJ.findIndex(item => item?.properties?.id === payloadId),
          1,
        );
        propertiesGJ.push(toBeReplaced);
        console.log('PUSH_MAP_MARKER_TOP_TOP1', toBeReplaced, propertiesGJ);
        featureCollectionNewProps = Object.assign(
          {},
          {
            type: 'FeatureCollection',
            features: propertiesGJ,
          },
        );
      } catch ({message}) {
        console.log('PUSH_MAP_MARKER_TOP_TOPERROR', message);
      }

      return {
        ...state,
        geoJSONPropertiesData: featureCollectionNewProps,
      };

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs: Author Feedback Needs: Repro This issue could be improved with a clear list of steps to reproduce the issue. wontfix This will not be worked on
Projects
None yet
Development

No branches or pull requests

4 participants