How to draw square to tag an object. (React Native) - reactjs

I see many mobile apps having a feature that user can draw a square to indicate something to tag on the image.
I'm building Face Tagging app and basically user draws square on the boundary of human's face on the image.
I've Googled it many times but I'm not sure RN has some feature library for tagging.
How can I achieve this? Is there any good library to draw square on image? And it will be even better if I can remember its coordinate width, height, and position of the rectangle.
Any idea or suggestion will be highly appreciated!
This is an example below

You can use React Native ART library to draw shapes on top of image. It is a standard library that ships with React Native (although not linked to the binary by default).
Regarding the algorithm:
Render an image
Overlay image with React Native's ART.Surface
Detect taps to get coordinates + overlay the rest of the image
Once you have coordinates of the tap, you can draw a shape you want
Stop drawing shape when user removes his finger (onPressOut event)
Where to go from here:
Basic tutorial, explaining how to wire up React Native and ART
Unofficial(there no official one) documentation

You can add children (in your case, square Views) to an Image tag, so you could do something like
<Image src={...}>
<View
style={{
position: 'absolute',
top: 120
left: 100,
height: 50,
width: 50,
borderWidth: 1
}}
/>
</Image>
You can get the x and y coordinates with the PanResponder API instead of hardcoding the top and left style properties
Edit: RN 50=< Removed support of nested content inside , use ImageBackground instead

I'd suggest looking into react-native PanResponder, or react-native-gesture-handler PanGestureHandler. It is a component that responds to touch input, and calculates the x and y values when you drag your finger, it will also tell you the distance travelled from where the finger started.
You can use this data and pass the x and y travel distance back into the width and height value of a View component to make it drag out a box with your finger.
EDIT:
Here's something I just put together as a bit of an experiment using react-native-gesture-handler.
import { View } from 'react-native';
import { GestureEvent, PanGestureHandler, PanGestureHandlerEventPayload } from 'react-native-gesture-handler';
const Test = () => {
const [start, setStart] = useState<{ x: number; y: number }>(null);
const [end, setEnd] = useState<{ x: number; y: number }>({ x: 0, y: 0 });
const [dimensions, setDimensions] = useState<{ w: number; h: number }>({ w: 0, h: 0 });
const onPress = (event: GestureEvent<PanGestureHandlerEventPayload>) => {
const { x, y, translationX, translationY } = event.nativeEvent;
if (!start) setStart({ x: y, y: x });
setDimensions({ w: translationX, h: translationY });
};
const onEnd = () => {
if (!start) return;
setEnd(start);
setStart(null);
};
return (
<View style={{ flex: 1 }}>
<PanGestureHandler onGestureEvent={onPress} onEnded={onEnd}>
<View style={{ width: '100%', height: '100%', backgroundColor: 'red' }}>
<View
style={{
position: 'absolute',
backgroundColor: 'blue',
top: start?.x ?? end?.x,
left: start?.y ?? end?.y,
width: dimensions?.w ?? 0,
height: dimensions?.h ?? 0,
}}
/>
</View>
</PanGestureHandler>
</View>
);
};
export default Test;

Related

Interpolated opacity is not initially invisible

I am trying to create an interpolation on opacity. I create the clock, and decide the start time and end time of the animation. I interpolate it so it starts at 0, however my view is visible. Do you know why it's visible? Is it the clock is created not at default of 0 but of current time? Is it possible to initialize my animStartTime at the same value as clock?
Here is an expo showing the problem and code and screenshot below. We see a black square of 48x48. It should be invisible but its at full 100% opacity.
https://snack.expo.io/#noitidart/reanimated-interpolation-not-initially-invisible
import React from 'react';
import { View, StyleSheet } from 'react-native';
import Reanimated from 'react-native-reanimated';
export default function App() {
const animClock = new Reanimated.Clock();
// determine the times it will run from
const duration = 1400;
const animStartTime = new Reanimated.Value(0);
const animEndTime = Reanimated.add(animStartTime, duration);
// create the opacity
const animFrom = new Reanimated.Value(0);
const animTo = new Reanimated.Value(1);
const opacity = Reanimated.interpolate(animClock, {
inputRange: [animStartTime, animEndTime],
outputRange: [animFrom, animTo],
extrapolate: Reanimated.Extrapolate.CLAMP,
});
return (
<View style={{ justifyContent: 'center', alignItems: 'center', flex: 1 }}>
<Reanimated.View
style={{ width: 48, height: 48, backgroundColor: 'black', opacity }}
/>
</View>
);
}
I modified some part of your code and you can see it there

React-three-fiber with react-spring animations

sorry if this seems like an obvious answer, but I am trying to create an animation similar to this. I am trying to create one similar with react-three-fiber but I can't use the ones shown in the docs since they manipulate the style transform property which isn't possible with three.js. I have tried to manipulate it through the position attribute and props but it comes with an error as shown here and I don't really know where to start to fix this. Here is my code:
const props = useSpring({
to: async next => {
await next({ position: [100, 100, 100] });
await next({ position: [50, 50, 50] });
},
from: { position: [100, 100, 100] },
config: { duration: 3500 },
reset: true
});
return (
<Canvas>
<a.group {...props}>
<Box position={[-1.2, 0, 0]} />
<Box position={[1.2, 0, 0]} />
</a.group>
</Canvas>
)
I have used react-spring successfully with hovering and scale but it just doesn't work when using position.
i think something like that is best done with trigonometry
const ref = useRef()
useFrame(() => {
ref.current.position.y = Math.sin(state.clock.getElapsedTime()) * 10
})
return <group ref={ref}> ...
Math.sin will yield a value between -1 to 1 and alternates smoothly between the two. multiply your factor (the distance) and you have it. here's an example that dollies the camera like that: https://codesandbox.io/s/r3f-lod-e9vpx
otherwise it's react-spring/three, not react-spring: https://codesandbox.io/s/clever-bartik-tkql8

Pull Scrollview to reveal View - React Native

I'm trying to build something similar to IMessage's and WhatsApp's header in react native, where users can pull down to reveal a search bar in the header.
I have been able to pull down to reveal a hidden input, but because the scrollview's y value becomes negative on pull, it will bounce back to y = 0 and prevent the input from sticking to the top. I have tried using both translateY and scaleY to reveal the hidden input.
class List extends Component {
scrollY = new Animated.Value(0)
render() {
const translateY = this.props.scrollY.interpolate({
inputRange: [ -50, 0 ],
outputRange: [ 50, 0 ],
extrapolate: 'clamp',
})
return (
<>
<Animated.View style={[
styles.container,
{ transform: [ { translateY } ] },
]}>
<Input />
</Animated.View>
<Animated.ScrollView
onScroll={Animated.event(
[ { nativeEvent: { contentOffset: { y: this.scrollY } } } ],
{ useNativeDriver: true }
)}
scrollEventThrottle={16}
>
{...}
</Animated.ScrollView>
</>
)
}
}
const styles = StyleSheet.create({
container: {
backgroundColor: colors.white,
width: windowWidth,
height: 50,
position: 'absolute',
top: -50,
zIndex: -99,
},
});
I found this Stack Overflow post that has been useful to reference but it is IOS specific Pull down to show view
I solved this by using contentOffset and without any animations. I needed to make sure the scrollview was at least the size of the phone's windowHeight and then used contentOffset to push the initial y value of the Scrollview to the size of the header
<ScrollView
ListHeaderComponent={() => (
<Header headerHeight={hiddenHeaderHeight} />
)}
contentContainerStyle={{ minHeight: windowHeight }}
contentOffset={{ y: hiddenHeaderHeight }}
...
This solution works for a Flatlist as well.
One thing to note is contentOffset is an ios specific prop
check out this medium article. It provides a detailed explanation of how to do something similar to your desired behavior.

React Native: How to stop map markers from re-rendering on every state update

I have a component that has a map with multiple custom markers for various locations and a carousel with cards for those same locations. When a user presses a marker, it should show the callout and show the location's name next to the marker (but outside of the callout).
However, because I update the state in onRegionChangeComplete, if the user moves the map and then quickly presses the marker (before the state finishes updating from calling setState in onRegionChangeComplete), then the markers will re-render before firing the onPress event, and the event is never fired.
One solution might be to use shouldComponentUpdate, however, the docs state that it should only be used for performance optimization and not to prevent re-renders (https://reactjs.org/docs/react-component.html#shouldcomponentupdate), but more importantly, my componentDidUpdate function has some conditional logic dependent on the region set in shouldComponentUpdate, as well as other conditional actions, so I don't want to prevent re-rendering of the entire component, just unnecessary re-rendering of the markers.
I'm also using the performance optimization mentioned in https://github.com/react-native-community/react-native-maps/issues/2082 of wrapping the makers in a component that implements shouldComponentUpdate and getDerivedStateFromProps, however, I'm not entirely sure this is doing anything because it seems like the parent component is just recreating all of my optimized markers rather than using their optimizations to handle re-rendering. Also, even if I don't use a wrapped marker but a conventional custom marker, I still have the same issues.
I've also opened an issue for this on react-native-maps but haven't gotten a response yet: https://github.com/react-native-community/react-native-maps/issues/2860
My 'onRegionComplete' function that updates state when map is moved. I removed a few other conditional state updates for brevity:
onRegionChangeComplete = (region) => {
const nextState = { };
nextState.region = region;
if (this.state.showNoResultsCard) {
nextState.showNoResultsCard = false;
}
.
.
.
this.setState({ ...nextState });
this.props.setSearchRect({
latitude1: region.latitude + (region.latitudeDelta / 2),
longitude1: region.longitude + (region.longitudeDelta / 2),
latitude2: region.latitude - (region.latitudeDelta / 2),
longitude2: region.longitude - (region.longitudeDelta / 2)
});
}
MapView using the more conventional marker (not the optomized version):
<MapView // show if loaded or show a message asking for location
provider={PROVIDER_GOOGLE}
style={{ flex: 1, minHeight: 200, minWidth: 200 }}
initialRegion={constants.initialRegion}
ref={this.mapRef}
onRegionChange={this.onRegionChange}
onRegionChangeComplete={this.onRegionChangeComplete}
showsUserLocationButton={false}
showsPointsOfInterest={false}
showsCompass={false}
moveOnMarkerPress={false}
onMapReady={this.onMapReady}
customMapStyle={mapStyle}
zoomTapEnabled={false}
>
{this.state.isMapReady && this.props.places.map((place, index) => {
const calloutText = this.getDealText(place, 'callout');
return (
<Marker
tracksViewChanges
key={Shortid.generate()}
ref={(ref) => { this.markers[index] = ref; }}
coordinate={{
latitude: place.getLatitude(),
longitude: place.getLongitude()
}}
onPress={() => { this.onMarkerSelect(index); }}
anchor={{ x: 0.05, y: 0.9 }}
centerOffset={{ x: 400, y: -60 }}
calloutOffset={{ x: 8, y: 0 }}
calloutAnchor={{ x: 0.075, y: 0 }}
image={require('../../Assets/icons8-marker-80.png')}
style={index === this.state.scrollIndex ? { zIndex: 2 } : null}
>
{this.state.scrollIndex === index &&
<Text style={styles.markerTitle}>{place.getName()}</Text>}
<Callout onPress={() => this.onCalloutTap(place)} tooltip={false}>
<View style={{
borderColor: red,
width: 240,
borderWidth: 0,
borderRadius: 20,
paddingHorizontal: 8,
flexDirection: 'column',
justifyContent: 'flex-start',
alignItems: 'center'
}}
>
<Text style={styles.Title}>Now:</Text>
<View style={{
width: 240,
flexDirection: 'column',
justifyContent: 'space-evenly',
alignItems: 'flex-start',
paddingHorizontal: 8,
flex: 1
}}
>
{calloutText.Text}
</View>
</View>
</Callout>
</Marker>
);
})
}
</MapView>
My function for the marker's on press event:
onMarkerSelect(index) {
this.setState({ scrollIndex: index });
this.carousel._component.scrollToIndex({
index,
animated: true,
viewOffset: 0,
viewPosition: 0.5
});
this.markers[index].redrawCallout();
}
Updating state and then quickly pressing a marker will cause the onPress event not to fire. Also, the markers are re-rendered/recreated every time a parent component is updated. (I say recreated because it seems like the markers are re-rendering without even firing shouldComponentUpdate or componentDidUpdate).
Is there any way to update state in onRegionChangeComplete without forcing the markers to re-render?
For anyone else who happens to have this problem, the issue was that I was randomly generating the keys for the markers, causing the parent component to create new markers each time it was re-rendered.
Specifically, the line key={Shortid.generate()} was the problem.

How to translateX, translateY to a certain coordinate rather than a relative point?

My question is about using translateX and translateY in react and/or react-native animations, to animate an object to a certain point.
These two transformations move an object relative to the existing point.
But for the scenario, the existing coordinate is not important and I want to assure the object moves to a certain point wherever it may exist in the screen.
Additional limitation is, I can not animate styles top, bottom, left and right because in react-native, if we animate these styles then we can not use the useNativeDriver={true} directive which causes performance problems.
You can use the onLayout prop to get position (relative to parent) and height/width on any View component.
Something like this should work. You might need to get position of more parent View components, depending on your current structure.
componentDidMount() {
this.animatedValue = new Animated.Value(0);
}
animate() {
const { parentYPosition } = this.state;
Animated.Timing(this.animatedValue, {
toValue: FINAL_POSITION - parentYPosition
}).start();
}
render() {
return (
<View
onLayout={event => {
const { y } = event.nativeEvent.layout;
this.setState({parentYPosition: y})
}}
>
<Animated.View
style={{
position: 'absolute',
top: 0,
transform: [
{
translateY: this.animatedValue
}
/>
/>
);
}
https://facebook.github.io/react-native/docs/view#onlayout

Resources