Plotting points on an image and rendering on a different screen size in React Native - react-native

So I have an image of a field in which the user is able to plot points by touching their screen. These points are then stored within a database by obtaining the e.nativeEvent.locationX, e.nativeEvent.locationY on the user's press.
So when I plot the point on my Tablet and review where I placed the plot on the field image using my PC, It is displayed incorrectly. I assume this is due to different screen sizes and the image is a different size depending on the device screen size.
How do I resolve this issue so that the plots are consistent no matter what device you are using?
Any other possible solutions are much appreciated.
Below is how I display the image / obtain the user's x-axis and y-axis values and on press these values are retrieved by the function ObtainPosition
<TouchableOpacity onPress={this.ObtainPosition}>
<Image style = {{
width:wp('60%'),
height:hp('100%'),
resizeMode: 'contain'}} source={require('./pitch.png')}/>
</TouchableOpacity>
note - you may see wp and hp this is an import
import {widthPercentageToDP as wp, heightPercentageToDP as hp} from 'react-native-responsive-screen';
Below is how I display user's plots (Note this is usually on a different screen/component). These plots are retrieved from the database and displayed in an array of objects. As you can see below the .map function loops through the array called PlotsArray and retrieves the x and y values and returns a View which is our plots.
<View>
<Image style = {{
width:wp('60%'),
height:hp('100%'),
resizeMode: 'contain'}} source={require('./pitch.png')}/>
{this.state.PlotsArray.map((data) => {
return (
<View
style={{
position: 'absolute',
left: data.x,
top: data.y,
backgroundColor:'#242424',
width: 10,
height: 10,
borderRadius: 50
}}>
</View>
)
})}
</View>

I think your obtainPosition function will need to take into account the Image element's left and right values (from the window / page) at the same time as you getting the actual x y coordinates of the click (if its padded, or the user is scrolled etc.)
You will also need to get the size of the image when you log the coordinates. So for example have a function similar to:
(event) => {
var {height, url, width} = event.nativeEvent.source;
}
called within the onLoad prop of the image. That way when you store the x, y co-ordinates you can also record the size of the image at the time (say 200px x 200px). Later on, when you render the clicks overlaid on the image, you can asses the size of the image on the 'viewing' device and adjust x y coordinates accordingly or store them as percentages rather than fixed pixels.
Rough example here of how using % on top and left values can adjust to the image varying in size https://jsfiddle.net/skfroxyt/ (have a play with editing the image height and width)

Related

Display small portions of image in flat list - small images are not visible

I am trying to display small rectangular portions of an image within a flat list - each row in the list contains a small portion of the image with a description. I am given a list of objects that contain a description as well as 4 (x,y) coordinates indicating the top left and bottom right portion of an image (i.e. bounding boxes).
The bounding boxes are all different sizes and in order to fill all available space within each flatlist row I am scaling the image according to the width/height of the given bounding box.
const objectWidth = item.x2 - item.x1;
const objectHeight = item.y2 - item.y1;
const heightScalingFactor = windowHeight / objectHeight;
const widthScalingFactor = windowWidth / objectWidth;
const imageStyle = {
backgroundColor: 'blue',
marginLeft: -item.x1 * widthScalingFactor,
marginTop: -item.y1 * heightScalingFactor,
position: 'absolute',
flex: 1,
//size of image
width: IMAGE_WIDTH * widthScalingFactor,
height: IMAGE_HEIGHT * heightScalingFactor,
};
This solution works fine for most of the bounding boxes but when the bounding box is especially small the image does not show up and is totally blank. I cannot figure out why this is. In the partially working expo example here: https://snack.expo.dev/#melampus123/flatlistwithimages all the images render correctly except for the "right ear" which does not show up at all.
In order to display the small portions of each image I am working off this related stack overflow answer. Please help me find a way to display even small bounding boxes within my flatlist. I found this website useful for marking specific (x,y) points on an image.

React native responsive units

I have been watching Rect Native tutorials on youtube and everyone uses units like padding:20 or fontSize:18 etc
Is that how you do in a professional app too? What is the ideal practice? Is this automatically responsive?
No, this is not automatically responsive. If we set padding:20, then the padding of that component stays on 20 no matter what phone we use. The same holds for font sizes, even though they are scaled depending on what pixel density we are dealing with.
A responsive design must be implemented by hand which could be done by using Dimensions as follows.
import { Dimensions } from 'react-native'
const width = Dimensions.get('window').width
const height = Dimensions.get('window').height
We can now use a different padding depending on the width of our device.
padding: width > 320 ? 20 : 15
When it comes to general layouting, then flexbox takes care of responsiveness, e.g. imagine that we have three elements on the screen with flexDirection: 'row' and we set flex:1 for all of them, then the available space will be divided equally between them no matter what our screen width is. The same applies for height.
It might be advised to create a separate Fonts file which holds font sizes and similar constants in a common place, so we only need to change them once.
import { Dimensions } from 'react-native'
const width = Dimensions.get('window').width
const height = Dimensions.get('window').height
export Fonts = {
h1: width > 320 ? 18 : 15,
h2: width > 320 ? 16 : 14,
...
}
The above takes care of responsive font sizes and we can use it in all other screens, e.g.
<Text style={{fontSize: Fonts.h1}}>I am a header</Text>

Fixed View at bottom (variable height) and fill remaining height with other View

I want to build a chat. In the view for the messages are the messages and the input field for the message. The Inputbox should be at the bottom and should use the height it needs. The height of the Inputbox can change if the user enters multi-line text. The msgs-View should fill the rest of the height (above). I don't want to position the Input absolute because the message's FlatList should be always in the "visible" area.
<View style={styles.wrapper}>
<View style={styles.msgs}>Messages (FlatList)</View>
<View style={styles.input}>Inputbox</View>
</View>
Don't give flex style to your input.
Give your wrapper and list a flex: 1 style. Wrapper will take up all the space in its parent (which I assume is the screen). And list will take all the space in wrapper. And input will sit in the bottom.

Slower performance on iOS release than development

I've built a React Native iOS app which is fairly basic; it's a few screens which the user can click through to from a 'Home' component, and each one consists of basic components comprising solely Text/View/Image components.
In simulator the app is responsive and there aren't any JS console warnings, however when I do a release to my iPad (Air 2), there's a noticable lag between the home screen and certain other screens. These are notably the screens which have more images on.
I'm wondering if it's because I'm using larger images (the app was designed for the iPad Pro 2) and scaling the images down for use where I want them. The worst offender is a page which has a masonry-style grid of images. There's still only about 30 in a ScrollView there though. Once the component has been shown once the app is much more responsive.
I've already taken steps to optimise my components in terms of using extending PureComponent or using stateless functions wherever possible, and console logging shows me that the touchables are responding immediately, so the delay is definitely at the render time of the component.
N.B. All images are local (loaded via require('./path/to/file')), nothing is being loaded over the network.
Here's an example of the code that populates an array of items for display inside the ScrollView:
...
const items = mediaItems.map((item, index) => {
// scale desired width (1044 for video, 520 for images) based on designed width and device aspect ratio.
const imageWidth = item.video ? (1044/2732) * deviceWidth : (520/2732) * deviceWidth
return (
<TouchableHighlight key={index} onPress={() => onOpen(item)}>
<View style={[styles.gridImageView, { width: imageWidth }]}>
<Image style={styles.gridImage} source={item.image} />
</View>
</TouchableHighlight>
)
});
...
and the styles for the gridImageView and gridImage are as follows:
...
gridImageView: {
height: (460/2732) * width,
margin: (2/2732) * width,
position: 'relative'
},
gridImage: {
resizeMode: 'cover',
width: null,
height: null,
flex: 1,
alignItems: 'center',
justifyContent: 'center'
},
...
So my question is kind of multi-layered:
What is the best practice with regards to ensuring the component appears quickly?
Should I not be setting a width/height on the image itself at all?
Should I be doing some kind of pre-loading of the images in the sizes I want before I let the user begin to navigate around the app?
Should I be using JPG instead of PNG images?
Is there another trick I'm missing?
Should I not be setting a width/height on the image itself at all
You have to set a width and height. If you don‘t do so, your image won‘t display
Should I be doing some kind of pre-loading of the images in the sizes I want before I let the user begin to navigate around the app?
It is a good idea to downlod images beforehand. Huge images need a lot of performance. Probably your issues are gone, if you resize your images before displaying them. Therefore you could use react native image resizer
Should I be using JPG instead of PNG images?
You should use JPGs, because they provide a higher compression rate.

How to dynamically set height of view based on its width by keeping aspect ratio?

I want to have a view whose width is responsive to its container width while keeping its aspect ratio.
I don't want to hard code width or height, but keep it dynamic and responsive so that it can be reused in multiple situations as a reusable component.
I hope it works similar to the following code:
<View style={{
alignSelf: 'stretch',
aspectRatio: 1.5
}} />
It should fill its container's width and set its own height dynamically based on its width.
Is this possible in react-native with flexbox?
As Hendy Irawan commented, React Native added aspectRatio property to StyleSheet.
https://facebook.github.io/react-native/docs/layout-props.html#aspectratio
From the document:
aspectRatio?: number
Aspect ratio control the size of the undefined dimension of a node.
Aspect ratio is a non-standard property only available in react native
and not CSS.
On a node with a set width/height aspect ratio control the size of the
unset dimension On a node with a set flex basis aspect ratio controls
the size of the node in the cross axis if unset On a node with a
measure function aspect ratio works as though the measure function
measures the flex basis On a node with flex grow/shrink aspect ratio
controls the size of the node in the cross axis if unset Aspect ratio
takes min/max dimensions into account
Thus the following should work:
const styles = StyleSheet.create({
view: {
aspectRatio: 1.5,
}
});
// later in render()
<View style={styles.view} />
Yes, you can use the Dimensions API to get the window's width, and then set height programatically. I typically call the dimensions as soon as the app loads, before you need to know the width, and then just pass the object around as needed.
This could be a custom component to accomplish your use case.
import {Dimensions} from 'react-native'
...
const AspectView = ({style, aspectRatio, children, ...props}) => {
let {height, width} = Dimensions.get('window');
return (
<View style={[style, {height: width * aspectRatio}] {...props}>{children}</View>
)
}