react-native-maps - polygon onPress event - react-native

I'm using react-native-maps and I'm rendering Polygons on my map that I want to be pressable.
My problem is that AFAIK react-native-maps Polygon doesn't have an onPress event.
I also thought about wrapping each Polygon with a Touchable component, but then my problem is that (again, AFAIK...), overlays in react-native-maps have to be direct children of the MapView component or else they won't render...
Any way to get this working?
Thanks!
Uri

Now there is an onPress event on the Polygon.
Reference: https://github.com/react-community/react-native-maps/blob/master/docs/polygon.md
<Polygon
coordinates={this.props.coordinates}
strokeColor="rgba(0, 0, 0, 1)"
strokeWidth={3}
tappable={true}
onPress={() => this.onPress('foo')}
/>

Ok, so I got this answer from https://github.com/airbnb/react-native-maps/issues/488.
The idea is to add an onPress event to the MapView component like so:
<MapView onPress={ this.mapPressed }>
Then, using geojson-js-utils you can do (double array is on purpose, that's the way the function works):
import { pointInPolygon } from 'geojson-utils';
mapPressed(event) {
const pointCoords = [event.nativeEvent.coordinate.longitude,
event.nativeEvent.coordinate.latitude];
const polygonCoords = [ [lngA, latA], [lngB, latB] ... etc... ];
const inPolygon = pointInPolygon(
{ 'type': 'Point', 'coordinates': pointCoords },
{ 'type': 'Polygon', 'coordinates': [plotCoords] }
);
if (inPolygon) {
// do something
}
}

I was working on this today and my workaround for now is to place a separate MapView.Marker component on the same coordinates, which does have an onPress prop assigned.
You'll have to make an effort to have it match the polygon or some other similar behaviour.
I'll follow this question for better solutions anyway :)

Related

React-leaflet problem to create <Marker> <Popup> instance dynamic

I would like to create a react-leaflet object dynamically. Normal leaflet objects work, but react-leaflet do not.
Code example on:
https://codesandbox.io/s/show-problem-react-leaflet-marker-popup-object-hm0o8?file=/src/MapView.js
Left click shows the desired behavior with leaflet objects and right click the problem with react-leaflet.
problem code:
var lat = e.latlng.lat;
var lon = e.latlng.lng;
// create popup contents
// use <Marker> from react-leaflet
var newMarker = <Marker position={[lat, lon]}>
{/* use Edit Popup */}
<Popup editable open>
Your stylable content here.
</Popup>
</Marker>;
// Problem addTo(map)
newMarker.addTo(map);
newMarker is a react element. You can't call the leaflet method addTo on it, because it is not a leaflet L.Marker instance.
If you want to be able to manage the markers through react, you would need to keep a state variable which is an array of coordinates. On map click, you can add the coordinate to that array, and then render a series of <Marker /> elements from that.
In your event handlers, you're simply going to capture the position of the click and pass it to a callback setMarkers:
function MyRightClickEventHandler({ setMarkers }) {
useMapEvents({
contextmenu: (e) => {
setMarkers(e.latlng);
}
});
return null;
}
setMarkers is a callback to setState on your primary MapView component, which adds the latlng to the state variable, which contains an array of latlngs:
// inside you'r MapContainer:
<MyRightClickEventHandler
setMarkers={(markers) =>
this.setState({
MarkerArray: [...this.state.MarkerArray, markers]
})
}
/>
Then map over the latlngs that are in that state variable, if there are any:
{this.state.MarkerArray &&
this.state.MarkerArray.map((latlng) => (
<Marker position={latlng}>
<Popup editable removable>
Thanks for using my editable popup plugin!
</Popup>
</Marker>
))}
Working codesandbox
Note that if you're going to use the editable, removable, or open props on editable popups rendered dynamically from an array, be sure to read my section on using editable popups rendered from a dynamic state array - it can get hairy if you're not careful. Feel free to ask questions in the comments if you run into problems.

Is there a way to animate the increased size of a View when new children are added?

I’m currently using LayoutAnimation to animate a view when children are added. However, since LayoutAnimation causes everything to be animated, globally, and I can’t easily use built-in Animated library to fit my use-case, I’m wondering if react-native-reanimated is able to help.
Here's a snack of my current solution:
https://snack.expo.io/#insats/height-adapation
This is what the result of that looks like:
Is there a way to achieve the same thing without using LayoutAnimation? I've looked through all exampled in react-native-reanimated, and I've read through the docs but I'm still not sure if this is possible to do or how I should get started. I've thought about using Animated to move the item-wrapper out of the viewable area and "scroll" it upwards (using transform translateY) when items are added, but that would require fixed height, which I don't have.
I have 2 approaches that I can suggest out of my mind:
You can configure your LayoutAnimation only when your desired state changed. If you use hooks it would be too easy:
const [state,setState] = useState([]);
useEffect(()=>{
/*rest code*/
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
},[state])
Or if you use class component you can catch your desired state change in componentDidUpdate:
componentDidUpdate(prevProps,prevState){
if(prevState.items!==state.items){
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
}
}
You can use onLayout function of view:
addItem = () => {
this.setState({
items: [...this.state.items, {title:'An item',isNew:true}]
})
};
renderItems = () => {
return this.state.items.map((item, index) => {
let opacity = new Animated.Value(0);
return (
<Animated.View onLayout={({nativeEvent})=>{
if(this.state.item.isNew){
// here you got the height from nativeEvent.layout.height
// Then you have to store the height animate height and opacity to its precise value
// PS I used opacity:0 to calculate the height
}
}} key={index} style={[styles.item,{opacity}>
<Text>{item.title}</Text>
</View>
)
});
};
When it comes to react-native-reanimated I regard it as more faster version of react-native's Animated library. So either way you will have to calculate the height!

Detect Swipe direction using React Native Gesture Handler and Reanimated

We have already developed a swiper using React Native PanResponder, (before somebody comment) - we cannot use any other swiper library here. In the swiper we developed, we are facing an issue when a user ends the swipe(meaning releases the Pan) there is a lag when starting a spring animation.
So to fix that, we are trying to move from React Native Animated API to Reanimated Libary which could solve the problem of lag the user is facing.
But while developing with React Native Gesture Handler (PanGestureHandler) and
Reanimated Library, we're unable to detect swipe direction in the
gesture handler.
I'm adding the part of code which we used to detect swipe direction in PanResponder
onPanMoving = (evt, {dx}) => {
this.draggedEnd = dx < -this.swipeThreshold; //You are moving towards right screen
this.draggedStart = dx > this.swipeThreshold; //You are moving towards left screen
}
As you see, detecting the direction while the pan is moving was so easy in PanResponder.
Here comes the problem with Gesture Handler,
I'm unable to detect swipe while the gesture state is active
I have already searched for issues in Gesture Handler and I found this.
There were two workaround suggested in the issue
First one is by Sonaye which has two handler and detects the
swipe direction in onHandlerStateChange for both the handlers
which was not helpful while using reanimated because it only sets swipe direction
when handlers state changes.
Second one is by Satya which actually detects the swipe when
the State is ACTIVE but he uses translationX property, the
translationX property can also negative for us and can move in
either direction similar to swiper.
Both the workarounds doesn't solve our problem.
So is there any other way to find direction using PanGestureHandler and Reanimated. I tried using PanResponder with Reanimated with dx value but ended up with error message i.e nativeEvent properties are only support as dx is from gestureState param from PanResponder.
Note: We cannot use FlingGestureHandler and Swipeables because we need to find direction in Fling still onHandlerStateChange and Swipeables doesn't use Reanimated.
You can detect direction when swipe horizontal via property velocityX
Example :
const handleGesture = (evt) => {
const { nativeEvent } = evt;
if (nativeEvent.velocityX > 0) {
console.log('Swipe right');
} else {
console.log('Swipe left');
}
};
return (
<PanGestureHandler onGestureEvent={handleGesture}>
// child component
</PanGestureHandler>
);
For those who want to use double FlingGestureHandler to have vertical or horizontal fling gesture here is the solution:
First you have to initialize the stateHandlers for the direction you want. In my case, i was trying to implement a vertical flingGestureHandler.
const onDownFlingHandlerStateChange = ({
nativeEvent,
}: FlingGestureHandlerStateChangeEvent) => {
if (nativeEvent.oldState === State.ACTIVE) {
progressHeight.value = withTiming(COLLAPSED_HEIGHT, {
duration: ANIMATION_DURATION,
});
}
};
const onUpFlingHandlerStateChange = ({
nativeEvent,
}: FlingGestureHandlerStateChangeEvent) => {
if (nativeEvent.oldState === State.ACTIVE) {
progressHeight.value = withTiming(
DEVICE_HEIGHT - TOP_PADDING_FOR_EXPANDED,
{
duration: ANIMATION_DURATION,
},
);
}
};
and then you have to bind those handlers to 2 FlingGestureHandler component of RNGH
<FlingGestureHandler
onHandlerStateChange={onUpFlingHandlerStateChange}
onEnded={() => {
onPressChecklistIcon();
}}
direction={Directions.UP}>
<FlingGestureHandler
direction={Directions.DOWN}
onEnded={() => {
onPressChecklistIcon();
}}
onHandlerStateChange={onDownFlingHandlerStateChange}>
<Animated.View style={[styles.container, reanimatedStyle]}>
<View style={styles.closerContainer}>
<View style={styles.closer} />
</View>
{renderChecklistContent()}
</Animated.View>
</FlingGestureHandler>
</FlingGestureHandler>
Use translationX property rather than velocityX property by nativeEvent:
const handleGesture = (evt) => {
const { nativeEvent } = evt;
if (nativeEvent.translationX > 0) {
Alert.alert('Swipe right');
} else {
Alert.alert('Swipe left');
}
};
In my case I have used double FlingGestureHandler to detect the direction. It's worked for me.

How to implement, React Native Drag-n-Drop

I want to implement react-native drag-and-drop, to swap component on the drop. supposed there is five component on a screen once I drag it should highlight dropable element. and once it is dropped the component should be swap.
In order to implement drag an drop you need to use the pan responders. First you need to define where are you going to store the values when the object is moving, you can set a property on the state.
state = {
pan: new Animated.ValueXY(),
};
Then you will have to create the pan responders in componentWillMount for example:
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder : () => true,
onPanResponderMove : Animated.event([null,{ // <--- When moving
dx : this.state.pan.x,
dy : this.state.pan.y
}]),
onPanResponderRelease : (e, gesture) => {} // <--- callback when dropped
});
Finally you need to set the left and top to the animated element in the render method.
<Animated.View
{...this.panResponder.panHandlers}
style={[this.state.pan.getLayout(), styles.circle]}>
<Text style={styles.text}>Drag me!</Text>
</Animated.View>
The this.state.pan.getLayout() returns the top and left, you need this in order to move the element around.
In order to swap the elements you need to implement the onPanResponderRelease.
For more detailed steps you can take a look at this tutorial: https://moduscreate.com/blog/animated_drag_and_drop_with_react_native

How to find x,y position of Component in React native

How do I find out the x,y position of where an element is positioned on the screen, I'm using React Native v0.14. I have tried the following but it says measure is not a function.
componentDidMount() {
setTimeout(this.measurePosition);
},
measurePosition() {
this.refs.q.measure((a, b, width, height, px,py ) => console.log(width))
},
Option1:
You can use the NativeMethodsMixin. Make sure you've added the mixin to your class
var NativeMethodsMixin = require('NativeMethodsMixin')
...
var MyComponent = React.createClass({
mixins: [NativeMethodsMixin]
...
componentDidMount: function(){
this.refs.element.measure((x,y,w,h,pX,pY) => console.log("dets"))
}
Option 2
You can use the onLayout property on Views. It is available on most components.
render:function(){
...
<View onLayout = {this.onLayout} />
...
},
onLayout:function(event){
console.log(event.nativeEvent.layout)
}
You will get the x,y, width and height
Can you use something like
1. get the browser DOM element using (ReactDOM.findDOMNode())
2. Use the elements getBoundingClientRect()
3. You have top, left, bottom and right coords
this may also be useful Retrieve the position (X,Y) of an HTML element