How do I stop React Native Maps' Draggable Marker from disabling touches outside the MapView on iOS using Google as the provider? - reactjs

On iOS with Google provider React Native Maps' Draggable Marker disables touches outside the MapView until it registers a touch inside the MapView. On Android everything is fine, but for some reason on iOS when I finish dragging a marker on the map and onDragEnd is called, no touch events are registered unless they are on TextInputs. Occasionally a TouchableOpacity will flash momentarily, but it's onPress function is never called. However if I touch inside the MapView, even nowhere near the marker, everything goes back to the way it's supposed to be. It's like react native maps has some finishing event that doesn't occur that forces the focus to stay on the map.
There are a couple tricky things going on but I don't think they're the culprits:
I use setState with onDragStart and onDragEnd to disable the ScrollView parent component, otherwise the dragging gets interrupted by the scroll on Android.
As part of onDragEnd I make a callout with react-native-geocoding, then update the region state. I have commented all this out and it still doesn't work.
The plan is to animate to the new region after the state is updated, but until this is resolved there's no point. Here's my code, or what's left of it after taking out all the commented stuff:
const MapSection = (props) => {
const {
location, setIsDraggingMarker, onDragMarkerEnd, region,
} = props;
const [isLoading, setIsLoading] = useState(false);
const onDragEnd = (e) => {
onDragMarkerEnd(e.nativeEvent.coordinate);
};
if (!isEmpty(location)) {
return (
<View style={styles.mapContainer}>
<MapView
provider={PROVIDER_GOOGLE}
style={styles.mapView}
initialRegion={region}
scrollEnabled={false}
zoomEnabled={false}
rotateEnabled={false}
>
<Marker
draggable
onDragStart={() => setIsDraggingMarker(true)}
onDragEnd={onDragEnd}
coordinate={region}
>
<Image
style={styles.imageStyle}
resizeMode="stretch"
source={require('../../../../assets/images/draggableMarkerPin.png')}
/>
<Callout
style={styles.customCallout}
onPress={() => { }}
>
<View style={styles.callout}>
<Feather
name="move"
size={12}
color="black"
/>
<Text style={{
paddingLeft: 4,
fontSize: 14,
}}
>
Press and drag to fine tune location!
</Text>
</View>
</Callout>
</Marker>
</MapView>
</View>
);
}
return null;
};
export default MapSection;
//FROM THE PARENT COMPONENT, PASSED AS PROPS
const onDragMarkerEnd = (coords) => {
setIsDraggingMarker(false);
setRegionWithLatLng(coords.latitude, coords.longitude);
};
const setRegionWithLatLng = async (latitude, longitude) => {
const fullLocationData = await geocodeLocationByCoords(latitude, longitude);
const currentLocation = { ...fullLocationData };
const newRegion = {
...region,
latitude: fullLocationData.geometry.location.lat,
longitude: fullLocationData.geometry.location.lng,
};
setRegion(newRegion);
dispatchEventDetailsState({
type: FORM_INPUT_UPDATE, value: currentLocation, isValid: true, input: 'location',
});
};
If there's any way to just trick the MapView into thinking it's been pressed (I'ved tried referencing the MapView and calling mapView.current.props.onPress(), no dice), then I'm fine with that. Just any workaround.

Related

How can I stop my google map reloading every time I try to do something else off the map?

I have React Page. I splitted the page in 2. In the first half I have a form with some fields, and In the second half I have a Google Map Component. I added a Polyline. The path of the polyline gets updated every time the user left clicks (add point) or right clicks (remove point), or if it's dragged. Everything works well until I leave the map and focus on inputs or press some buttons in the other half. Then if I try to add new points or remove them ( this is not working because it has no points to remove) it starts a new polyline.
My methods are very simple, I took them from the docs and they do the job.
const addLatLng = (event) => {
const path = poly.getPath();
path.push(event.latLng);
}
const removeVertex = (vertex) => {
let path = poly.getPath();
path.removeAt(vertex)
}
// eslint-disable-next-line no-undef
google.maps.event.addListener(poly, 'rightclick', function (event) {
if (event.vertex === undefined) {
return;
} else {
removeVertex(event.vertex)
}
})
<div style={{ height: '100vh' }}>
<GoogleMap
center={center}
zoom={3}
onLoad={(map) => setMap(map)}
mapContainerStyle={{ width: '100%', height: '100%' }}
onClick={(event) => { !showInputForLink && addLatLng(event) }}
>
</GoogleMap>
</div>
First methods do the action and this is how I declared the map.
const { isLoaded } = useJsApiLoader({
googleMapsApiKey: ct.MAPS_API_KEY,
libraries: ['drawing, places']
})
// eslint-disable-next-line no-undef
const [map, setMap] = useState(/** #type google.maps.Map */(null))
const poly = new google.maps.Polyline({
strokeColor: '#160BB9',
strokeOpacity: 1.0,
strokeWeight: 3,
editable: true,
});
poly.setMap(map)
And this is how I declared the poly.
I tried to look up in Docs to see if I missed something, but I couldn't find anything that will not lose the focus on the map when I do something else or to start another polyline.
I am not using different components, everything is in on file.
Should I declare a Poly component inside the map? and not use the traditional JavaScript method?
How can I create this without resetting the map when I do actions in the first half?

Why is my React Native component not re-rendering on state update?

I'm struggling with this React-Native component for a few days now. You should probably know that React-Native is kind of new to me so... sorry if the solution is obvious to you.
I'm using react-native-maps and I have several markers on my map. Each one of them has some data stored in my state and I want the callout to display a piece of this state on press.
Here are my states :
const [markersDetails, setMarkersDetails] = useState([]);
const [activeMarker, setActiveMarker] = useState({});
My activeMarker is updated by this function :
const markerSearch = (markerId) => {
let stockMarker = markersDetails.find((singleMarker) => {
return Number(singleMarker.idMarker) === markerId;
});
console.log("Stock ", stockMarker);
setActiveMarker(stockMarker);
console.log("State ", activeMarker);
};
And this function is called, inside my return, with the onPress of any marker :
<Marker
key={Number(marker.index)}
coordinate={{
latitude: Number(marker.latitude),
longitude: Number(marker.longitude),
}}
pinColor="blue"
onPress={() => {
markerSearch(Number(marker.index));
}}
>
{activeMarker !== {} && activeMarker.markerName && (
<Callout>
<View>
<Text>{activeMarker.markerName}</Text>
</View>
</Callout>
)}
</Marker>
But whenever I press on a marker, the callout opens immediatly while my state is not yet updated. So the text within the callout refers either to the previous marker or is empty (if it's the first marker I press on).
I've checked with console.log and my state is clearly updated but it takes a little bit more time. And I don't know why my callout is not re-rendering when this state is updating.
I've tried a ton of things to make this works but I can't figure this out...
Try doing something like that:
You can extract the section to a new component
Then inside this use the useEffect hook
export default function CalloutComponent({activeMarker}) {
const [markerName, setMarkerName] = useState('')
useEffect(() => {
setMarkerName(activeMarker?.markerName)
}, [activeMarker?.markerName])
if(!!markerName) return null
return (
<Callout>
<View>
<Text>{markerName}</Text>
</View>
</Callout>
)
}
And use this new component in your Main view
<Marker
...
>
<CalloutComponent activeMarker={activeMarker}/>
</Marker>

TextInput goes Missing, but Keyboard Stays After Every KeyStroke?

Trying to figure out an issue with the TextInput component I have which is acting weird. Basically, after every keystroke, the textinput seems to be losing its focus, but the keyboard seems to stay..
Im implementing it as a search bar that is triggered / animated when a search icon is touched by the user:
<TouchableHighlight
activeOpacity={1}
underlayColor={"#ccd0d5"}
onPress={onFocus}
style={styles.search_icon_box}
>
onFocus method:
const onFocus = () => {
setIsFocused(true);
const input_box_translate_x_config = {
duration: 200,
toValue: 0,
easing: EasingNode.inOut(EasingNode.ease)
}
const back_button_opacity_config = {
duration: 200,
toValue: 1,
easing: EasingNode.inOut(EasingNode.ease)
}
// RUN ANIMATION
timing(input_box_translate_x, input_box_translate_x_config).start();
timing(back_button_opacity, back_button_opacity_config).start();
ref_input.current.focus();
}
just a simple animation where when triggered the search bar will slide from the right side of the screen
<Animated.View
style={[ styles.input_box, {transform: [{translateX: input_box_translate_x}] } ]}
>
<Animated.View style={{opacity: back_button_opacity}}>
<TouchableHighlight
activeOpacity={1}
underlayColor={"#ccd0d5"}
onPress={onBlur}
style={styles.back_icon_box}
>
<MaterialIcons name="arrow-back-ios" size={30} color="white" />
</TouchableHighlight>
</Animated.View>
<SearchBar
placeholder='Search'
keyboardType='decimal-pad'
returnKeyType='done'
ref={ref_input}
value={searchText}
onChangeText={search}
onClear={onBlur}
onSubmitEditing={onBlur}
onFocus={() =>console.log("focus received" ) }
onBlur={() => console.log("focus lost") }
/>
</Animated.View>
Search
const search = (searchText) => {
setSearchText(searchText);
let filteredData = AnimalList.filter(function (item) {
return item.tag_number.toString().includes(searchText);
});
setFilteredData(filteredData);
}
So, when I clicked on the search icon, the search bar will present itself through animated view and the keyboard will automatically be focused. However, after entering a single character on the keyboard the searchbar just vanishes with keyboard still showing.
I tried to debug using onFocus={() =>console.log("focus received" ) } and it looks like the searchBar is still focused on, its just not showing
EDIT: Issue Video Here https://github.com/renwid/test/issues/1
You can show the full version of SearchBar to get more help
[Updated]
The initial step, Animated.Value must be
const input_box_translate_x = useRef(new Value(width)).current;
const back_button_opacity = useRef(new Value(0)).current;
instead of
const input_box_translate_x = new Value(width);
const back_button_opacity = new Value(0);
Because you using the function component and re-render might be re-create the component and the Animated.Value will be re-create too. So the Animated.Value can not keep the state and cause you issue

Scroll down to a specific View in React Native ScrollView

I want to be able to scroll a after pressing button so that it visible on screen. How can I tell a react-native ScrollView move to a certain?
Hello you can use the property scrollTo like below
import {useRef} from "react"
import {
ScrollView,
Button,
} from 'react-native';
const YouComp = () => {
const refScrollView = useRef(null);
const moveTo = () => {
refScrollView.current.scrollTo({x: value1, y: value2});
// or just refScrollView.current.scrollTo({x: value1}); if you want to scroll horizontally
// or just refScrollView.current.scrollTo({y: value2}); if you want to scroll vertically
}
return (<>
<Button onPress={moveTo} title="title" />
<ScrollView ref={refScrollView}>
...
</ScrollView>
</>);
}
You can set whether x or y value or both
Check the full doc here
First you need to create reference to element
this.scrollViewRefName = React.createRef()
Then pass it to ref attribute
<ScrollView ref={this.scrollViewRefName}>
Then you trigger the function from your button with scrollToTheEnd or wherever you want to scroll within the element
<View style={styles.ButtonContainer}>
<Button onPress={() => { this.scrollViewRef.current.scrollToTheEnd }} />
</View>
Note that you may need extra callback function in onPress depending on from which context you have the components
using ref and scrollTo is just bullshit and dose not always work.
Here is how i did it.
const [scrollYPosition, setScrollYPosition] = useState(0);
const [data, setData] = useState([]);
const goToItem = () => {
// lets go to item 200
// the 200 is the item position and the 150 is the item height.
setScrollYPosition(200 * 150);
}
<ScrollView contentOffset = {
{
y: scrollYPosition,
x: 0
}
}>
// papulate your data and lets say that each item has 150 in height
</ScrollView>

How to get a ref to the dom node of a google map's marker in react?

Using the google-maps-react npm package I can get a ref to the map's dom node like so:
loadMap() {
const maps = this.props.google.maps;
const mapRef = this.refs.map; <---- ref set in render function on dom node
const node = ReactDOM.findDOMNode(mapRef); <--- the dom node
...
this.map = new maps.Map(node, mapConfig);
this.mapRef = mapRef;
}
It's pretty easy because mapref is set in the render method:
render() {
return (
<div style={{ height: mapHeight }} ref="map">
...
And then that is used to set node and then that is used to new up the map.
How would I do this with a map's marker? A marker doesn't need a dom node to be created and therefore I cant get a ref to the marker.
this.marker = new google.maps.Marker({someOptions}); <----- no dom node needed
I want to do this because I need to dynamically change the icon of the marker based on some value in my redux store. I have tried changing the icon via props (see below), but it somehow prevents the icon marker from being draggable even though draggable is set to true.
return (
<Marker
key={foo}
position={latLngPos}
icon={ active ? activeIcon : defaultIcon }
draggable={true}
onDragstart={() => { return this.handleMapMarkerDragStart();}}
onDragend={() => { return this.handleMapMarkerDrop();}}
/>);
I suspect things are acting strangely because to get google's maps api to work with react, the components have to deal with the actual dom nodes instead of the virtual dom nodes.
Any insight into this would be much appreciated.
Regarding
I want to do this because I need to dynamically change the icon of the
marker based on some value in my redux store. I have tried changing
the icon via props (see below), but it somehow prevents the icon
marker from being draggable even though draggable is set to true.
the following example demonstrates how to:
set marker as draggable
set custom icon per marker
Example
const handleDragEnd = (props, marker, event) => {
console.log(event.latLng);
};
const defaultIcon =
"http://maps.google.com/mapfiles/kml/pushpin/blue-pushpin.png";
const activeIcon =
"http://maps.google.com/mapfiles/kml/pushpin/pink-pushpin.png";
const MapWrapper = props => {
return (
<div className="map-container">
<Map
google={props.google}
className={"map"}
zoom={4}
initialCenter={{ lat: -24.9923319, lng: 135.2252427 }}
>
{props.places.map((place, i) => {
const active = i % 2 === 0;
return (
<Marker
icon={active ? activeIcon : defaultIcon}
key={place.id}
position={{ lat: place.lat, lng: place.lng }}
draggable={true}
onDragend={handleDragEnd}
/>
);
})}
</Map>
</div>
);
};
Note: Marker component does not support onDragstart event
listener at the moment in google-maps-react library, but it could be
attached directly to Google Maps Marker object
Demo

Resources