react-native-draggable
react-native-draggable copied to clipboard
How to get accurate coordinates of the draggable
I've been playing with it for about a week now, and have been unable to get the coordinates accurately. It seems the getPosition method I saw in another issue report is no longer supported.
I am using: "react-native-draggable": "^3.1.0" which I believe to be the latest.
I have tried setting x to x += event.nativeEvent.dx (and similarly for y) I have tried x = event.nativeEvent.pageX (same for y) I have tried x = event.nativeEvent.pageX - event.nativeEvent.locationX (same for y) - to try to get the top left corner by subtracting the element offset location from the page offset location I have tried x = event.nativeEvent.dx (same for y) - because I read in one of the issues that reapplying the coords to an existing element will treat the x,y as offsets to the original.
In all cases, the element jumps around after i drop it, and the saved coordinates render it in a different location.
Please let me know what I am doing wrong!! Thanks.
#58 onDragRelease will pass the third argument "bounds" which has x,y at the top left corner.
I upgraded to 3.2 and there isn't a 3rd argument yet. Do you know when that will be merged into the master branch?
it will be 3.3.0. it's still in the progress. you can simply copy the code from #58 for your testing purpose
I ended up including your tongyy-patch-3 as a dependency instead of master, just to see if it resolves the problem I'm having. The coordinates I get out of the bounds are a lot closer to where I drop the element than before - so maybe the difference now is just something with my mapping of screen coordinates to video resolution coordinates. basically I'm putting overlays on video and then rendering the video with ffmpeg to encode the overlays on top of it.
But.. the issue where the draggable jumps around on the screen between state changes still seems to be there. I'll drop the element, which triggers a state change to save the new coordinates, which redraws the draggables, and it jumps to a different spot than where I dropped it. Sometimes it even jumps off the screen.
My onDragRelease call back is defined as:
function setCoords(event, gestureState, bounds, key) {
console.log('setting coords', bounds);
transforms[key].dx += gestureState.dx;
transforms[key].dy += gestureState.dy;
transforms[key].x = bounds.left;
transforms[key].y = bounds.top;
setTransforms(transforms);
}
And the draggables are being rendered like this:
let transform_list = [];
transforms.map((value, index) => {
let inner;
if (value.isSelected) {
inner = <TextInput style={styles.input} value={value.text} onChangeText={(text) => changeText(index, text)} />
} else {
inner = <TouchableOpacity><Text onPress={(event) => selectDraggable(event, index)} style={{ fontSize: value.fontSize, color: 'white' }}>{value.text}</Text></TouchableOpacity>
}
transform_list.push(<Draggable key={index} x={value.dx} y={value.dy}
onDragRelease={(event, gestureState, bounds) => setCoords(event, gestureState, bounds, index)}
onRelease={(event, wasDragging) => selectDraggable(event, index)}
>
{inner}
</Draggable>);
});
I've tried the x & y on the draggable as bounds.left and bounds.top directly, and i've tried them as the dx, dy directly, and I've tried the accumulated dx, dy as it is written above.
Do you have any tips to get the draggables to render at the location they were dropped after a state change is triggered?
the x,y from bounds are correct. I think the problem is the state inside Draggable or some native components should be reset in order to deal with the dynamic x,y as props passed into it.
can you try out this way? if you figure it out, PR welcome
Try out this way, meaning resetting state in Draggable or native component? Or did you mean something else? Sorry, just want to make sure I understand your suggestion!
In case it helps,
while using
transforms[key].x = bounds.left;
transforms[key].y = bounds.top;
If I move the the draggable a few pixels, for the sake of this example lets say I moved it 5 pixels, it jumps an extra 5 pixels. If I move the same draggable another 5 pixels, it jumps an extra 10 pixels, the 3rd time an extra 20, the 4th time an extra 40, etc. So there is some kind of accumulation compounding the movement.
Put another way, let's say I move the draggable 5 pixels to the right the first time, and 5 pixels to the bottom the second time, even though the second time I only moved it down, it jumps to the right an equal amount to how much i dragged it to the right for the first reposition.
yes, it's the right direction I think. we need to reset these accumulations in Draggable when x,y from props change.
@jeb218 Have you tried not using state for transforms?
I'd be curios to see if setTransforms(transforms) is even doing anything since they're referencially equal
i abstracted the state away into child components and was able to remove the dependency of Draggable on the state changes of x and y.
export default function DraggableExample(props) {
const [textBoxes, setTextBoxes] = useState([]);
return (
<View style={styles.container}>
<Video
....
/>
<TextBoxes textBoxes={textBoxes} setTextBoxes={setTextBoxes} />
<View style={styles.controlBar}>
<AddTextBox addTextBox={textBox => setTextBoxes([...textBoxes, textBox])} />
</View>
</View>
);
}
const AddTextBox = ({ addTextBox }) => {
const [textBox, setTextBox] = React.useState({})
return (
<View style={{ flex: 1, flexDirection: "row", justifyContent: "space-between", margin: 5 }}>
<TextInput style={{ backgroundColor: 'white', flex: 1, borderRadius: 10 }} value={textBox.text} onChangeText={value => setTextBox({
text: value,
x: width / 2,
y: height / 2,
})} />
<MaterialIcons name="check" size={35} color="white" onPress={() => { addTextBox(textBox); setTextBox(''); }} />
</View>
)
}
const TextBoxes = ({ textBoxes, setTextBoxes }) => (
<View style={styles.contentContainer}>
{textBoxes.map((textBox, index) => (
<Draggable key={index} x={width / 2} y={height / 2}
onDragRelease={(event, gestureState, bounds) => {
setTextBoxes(textBoxes => textBoxes.map((t, _index) => {
if (_index !== index) return t;
return { ...t, x: bounds.left, y: bounds.top }
}));
}}>
<View style={styles.draggableContent}>
<Text>
{textBox.text}
</Text>
</View>
</Draggable>
))}
</View>
)
v3.3.0 has published.
It works really well on an xcode debug build, but an archive release build throws :
Terminating app due to uncaught exception 'RCTFatalException: Unhandled JS Exception: TypeError: undefined is not an object (evaluating 'c.left')
The app.bundle code that relates to this is:
onDragRelease:function(t,l,c){o(function(t){return t.map(function(t,o){return o!==n?t:v({},t,{x:c.left,y:c.top})})})}}
that very closely matches the actual code, so i can be sure it is coming from the bounds object:
onDragRelease={(event, gestureState, bounds) => {
setTextBoxes(textBoxes => textBoxes.map((t, _index) => {
if (_index !== index) return t;
return { ...t, x: bounds.left, y: bounds.top }
}));
}}
have you tested the new parameter in onDragRelease in an archive release from xcode? The bounds parameter doesn't seem to be coming through
I have this patch to 3.3.0 that enables the callback with "bounds".
diff --git a/node_modules/react-native-draggable/Draggable.tsx b/node_modules/react-native-draggable/Draggable.tsx
index 363ab4a..3c75dfb 100644
--- a/node_modules/react-native-draggable/Draggable.tsx
+++ b/node_modules/react-native-draggable/Draggable.tsx
@@ -125,7 +125,7 @@ export default function Draggable(props: IProps) {
(e: GestureResponderEvent, gestureState: PanResponderGestureState) => {
isDragging.current = false;
if (onDragRelease) {
- onDragRelease(e, gestureState);
+ onDragRelease(e, gestureState, getBounds());
onRelease(e, true);
}
if (!shouldReverse) {