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

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>
)
}

Related

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>

Plotting points on an image and rendering on a different screen size in 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)

React Native view scaling

So I'm developing a cross platform React Native app, the app is using allot of images as buttons as per design requirements that need to be given an initial height and width so that their aspect ratios are correct. From there I've built components that use these image buttons and then placed those components on the main screen. I can get things to look perfect on one screen by using tops and lefts/ rights to get the components positioned according to the design requirements that I've been given.
The problem I'm running into is now scaling this main screen for different screen sizes. I'm basically scaling the x and y via the transform property on the parent most view as such. transform: [{ scaleX: .8 }, { scaleY: .8 }] After writing a scaling function that accounts for a base height and current height this approach works for the actual size of things but my positioning is all screwy.
I know I'm going about this wrong and am starting to think that i need to rethink my approach but am stumped on how to get these components positioned correctly on each screen without having to hard code it.
Is there any way to position a view using tops and lefts/rights, lock that in place, then scale it more like an image?
First of all, try using flex as far as you can. Then when you need extra scaling for inner parts for example, you can use scale functions. I have been using a scale function based on the screen size and the pixel density, and works almost flawless so far.
import { Dimensions } from "react-native";
const { width, height } = Dimensions.get("window");
//Guideline sizes are based on standard ~5" screen mobile device
const guidelineBaseWidth = 350;
const guidelineBaseHeight = 680;
const screenSize = Math.sqrt(width * height) / 100;
const scale = size => (width / guidelineBaseWidth) * size;
const verticalScale = size => (height / guidelineBaseHeight) * size;
const moderateScale = (size, factor = 0.5) =>
size + (scale(size) - size) * factor;
export { scale, verticalScale, moderateScale, screenSize };
Then you can import these and use in your components. There are different types of scales, you can try and see the best one for your components.Hope that helps.
I ended up going through each view and converting everything that was using a hard coded height and width pixel to setting the width and then using the property aspectRatio and giving that the hard coded height and widths. That along with implementing a scaling function that gave me a fraction of the largest view, so say .9, and then scaling the main view using transform. People arent kidding when they say this responsive ui stuff is tough.
2022 update -
I resolved this problem on my next app by using flex everywhere & a function called rem that I use everywhere that needs a fixed pixel count. With this I can set the width on an image and define an aspect ratio based on the images original dimensions and get an image that scales to the screen size, it's been super reliable.
static width = Dimensions.get("window").width;
static height = Dimensions.get("window").height;
static orientation = 'PORTRAIT';
static maxWidth = 428;
static rem = size => {
let divisor = window.lockedToPortrait || Styles.orientation === 'PORTRAIT' ? Styles.width : Styles.height;
return Math.floor(size * (divisor / Styles.maxWidth))
};
The maxWidth is a predefined value from the largest device I could find to simulate which was probably an iPhone max.

Hold Position of ScrollView when adding data

I am trying to make it so whenever something new is rendered into a scroll view, the scroll view will stay put and not bump up and down. Right now if a new component is rendered in, the scrollview appears to be reset to 0.
Is there a way to stop this behavior, and hold position?
Right now for the scrollview I am using:
handleScrollt(event){
this.scroll = event.nativeEvent.contentOffset.y
}
handleSizet(width, height){
if (this.scroll) {
const position = this.scroll + height - this.height
this.refs.sv.scrollTo({x: 0, y: position, animated: false})
}
this.height = height
}
<ScrollView
ref="sv"
scrollEventThrottle={16}
onScroll={this.handleScrollt.bind(this)}
onContentSizeChange={this.handleSizet.bind(this)}
The issue with this is the scrollview will render briefly, before then scrolling to the correct offset. So it seems like theres a brief splash of the top of the screen
you have to define a height for the scrollview.
If the scrollview is supposed to cover an entire View you can get the view's height by:
<View style={styles.contactView} onLayout={(event) => {
var contactViewY = event.nativeEvent.layout.y;
this.setState({contactViewY: contactViewY})
}}>
and then give it to the scrollview
<ScrollView style={[styles.contactScroller, {height: this.state.contactViewY}]}>
Bear in mind that the onLayout method is called immediately once the layout has been calculated, so if the view's height changes later, this two lines of code alone won't update it.
You should try maintainVisibleContentPosition.
Example:
<ScrollView
{...props}
maintainVisibleContentPosition={{
minIndexForVisible: 0,
}}
/>
From the docs:
maintainVisibleContentPosition
When set, the scroll view will adjust the scroll position so that the
first child that is currently visible and at or beyond
minIndexForVisible will not change position. This is useful for lists
that are loading content in both directions, e.g. a chat thread, where
new messages coming in might otherwise cause the scroll position to
jump. A value of 0 is common, but other values such as 1 can be used
to skip loading spinners or other content that should not maintain
position.

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.