I am working on tutorial for React Native navigation. I found out that all layout starts loading from top of screen instead of below of the status bar. This causes most layouts to overlap with the status bar. I can fix this by adding a padding to the view when loading them. Is this the actual way to do it? I don' think manually adding padding is an actual way to solve it. Is there a more elegant way to fix this?
import React, { Component } from 'react';
import { View, Text, Navigator } from 'react-native';
export default class MyScene extends Component {
static get defaultProps() {
return {
title : 'MyScene'
};
}
render() {
return (
<View style={{padding: 20}}> //padding to prevent overlap
<Text>Hi! My name is {this.props.title}.</Text>
</View>
)
}
}
Below shows the screenshots before and after the padding is added.
Now you can use SafeAreaView which is included in React Navigation:
<SafeAreaView>
... your content ...
</SafeAreaView>
There is a very simple way to fix this. Make a component.
You can create a StatusBar component and call it first after the first view wrapper in your parent components.
Here is the code for the one I use:
'use strict'
import React, {Component} from 'react';
import {View, Text, StyleSheet, Platform} from 'react-native';
class StatusBarBackground extends Component{
render(){
return(
<View style={[styles.statusBarBackground, this.props.style || {}]}> //This part is just so you can change the color of the status bar from the parents by passing it as a prop
</View>
);
}
}
const styles = StyleSheet.create({
statusBarBackground: {
height: (Platform.OS === 'ios') ? 18 : 0, //this is just to test if the platform is iOS to give it a height of 18, else, no height (Android apps have their own status bar)
backgroundColor: "white",
}
})
module.exports= StatusBarBackground
After doing this and exporting it to your main component, call it like this:
import StatusBarBackground from './YourPath/StatusBarBackground'
export default class MyScene extends Component {
render(){
return(
<View>
<StatusBarBackground style={{backgroundColor:'midnightblue'}}/>
</View>
)
}
}
I tried a more simple way for this.
We can get the height of Status Bar on android and use SafeAreaView along with it to make the code work on both platforms.
import { SafeAreaView, StatusBar, Platform } from 'react-native';
If we log out Platform.OS and StatusBar.currentHeight we get the logs,
console.log('Height on: ', Platform.OS, StatusBar.currentHeight);
Height on: android 24 and
Height on: android 24
We can now optionally add margin/padding to our container view using
paddingTop: Platform.OS === "android" ? StatusBar.currentHeight : 0
The final code in App.js is below:
export default class App extends React.Component {
render() {
return (
<SafeAreaView style={{ flex: 1, backgroundColor: "#fff" }}>
<View style={styles.container}>
<Text>Hello World</Text>
</View>
</SafeAreaView>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
paddingTop: Platform.OS === "android" ? StatusBar.currentHeight : 0
}
});
#philipheinser solution does work indeed.
However, I would expect that React Native's StatusBar component will handle that for us.
It doesn't, unfortunately, but we can abstract that away quite easily by creating our own component around it:
./StatusBar.js
import React from 'react';
import { View, StatusBar, Platform } from 'react-native';
// here, we add the spacing for iOS
// and pass the rest of the props to React Native's StatusBar
export default function (props) {
const height = (Platform.OS === 'ios') ? 20 : 0;
const { backgroundColor } = props;
return (
<View style={{ height, backgroundColor }}>
<StatusBar { ...props } />
</View>
);
}
./index.js
import React from 'react';
import { View } from 'react-native';
import StatusBar from './StatusBar';
export default function App () {
return (
<View>
<StatusBar backgroundColor="#2EBD6B" barStyle="light-content" />
{ /* rest of our app */ }
</View>
)
}
Before:
After:
The react-navigation docs have a great solution for this. First off, they recommend not to use the SafeAreaView included with React Native because:
While React Native exports a SafeAreaView component, it has some
inherent issues, i.e. if a screen containing safe area is animating,
it causes jumpy behavior. In addition, this component only supports
iOS 10+ with no support for older iOS versions or Android. We
recommend to use the react-native-safe-area-context library to handle
safe areas in a more reliable way.
Instead, they recommend react-native-safe-area-context - with which it would look like this:
import React, { Component } from 'react';
import { View, Text, Navigator } from 'react-native';
import { useSafeArea } from 'react-native-safe-area-context';
export default function MyScene({title = 'MyScene'}) {
const insets = useSafeArea();
return (
<View style={{paddingTop: insets.top}}>
<Text>Hi! My name is {title}.</Text>
</View>
)
}
I would like to note that it's probably a better idea to use the SafeAreaView that this library offers though, since phones these days may also have elements at the bottom that can overlap UI elements. It all depends on your app of course. (For more detail on that, see the react-navigation docs I linked to in the beginning.)
Here is a way that works for iOS:
<View style={{height: 20, backgroundColor: 'white', marginTop: -20, zIndex: 2}}>
<StatusBar barStyle="dark-content"/></View>
You can handle this by adding a padding to you navigation bar component or just ad a view that has the same hight as the statusbar at the top of your view tree with a backgroundcolor like the facebook app does this.
Just Simple User React native Default StatusBar to achieve this funcationality.
<View style={styles.container}>
<StatusBar backgroundColor={Color.TRANSPARENT} translucent={true} />
<MapView
provider={PROVIDER_GOOGLE} // remove if not using Google Maps
style={styles.map}
region={{
latitude: 37.78825,
longitude: -122.4324,
latitudeDelta: 0.015,
longitudeDelta: 0.0121,
}}
/>
</View>
If you combine SaveAreaView and StatusBar, you get it.
https://reactnative.dev/docs/statusbar
https://reactnative.dev/docs/safeareaview
Just do this:
<SafeAreaView>
<View style={{flex: 1}}>
<StatusBar translucent={false} backgroundColor="#fff" />
// Your dark magic here
</View>
</SafeAreaView>
[This answer is applicable to Android emulators]
Hi, I have imported status bar from "react-native" and called it at the end of block with status bar style set to auto and it worked for me, the code below is for reference:
import { SafeAreaView,Button, StyleSheet, Text, TextInput, View } from 'react-native';
import { StatusBar } from 'react-native';
export default function App() {
return (
<SafeAreaView style={styles.appContainer}>
<View >
<TextInput placeholder='Add your course goal' />
<Button title="Add Goals" />
</View>
<View>
<Text>List of goals..</Text>
</View>
<StatusBar style="auto" />
</SafeAreaView>
);
}
Related
Hi I know it's a known issue about the auto height of webview in react native,
and I have tried all the possibles solutions I've found on the internet such as :
https://gist.github.com/epeli/10c77c1710dd137a1335
https://github.com/danrigsby/react-native-web-container/blob/master/index.js
and all the solutions suggested in:
React native: Is it possible to have the height of a html content in a webview?
But unfortunately none of these seems to work for me,
I understand that the workaround they all suggest is to set the title to the height, but in my case it seems that the title always stays the same which is :
"text/html ...." and the rest of my html.
I get the html content from an API, it comes without a body, head or html tags, I've also tried adding these tags manually to the html and nothing seems to work.
I would love to hear if anyone else had that problem and how did it get fixed.
I wrap WebView inside a View, and set the height from the View.
<View style={{ height: 200 }}>
<WebView
automaticallyAdjustContentInsets={false}
source={{uri: 'https://player.vimeo.com/video/24156534?title=0&byline=0&portrait=0'}}
/>
</View>
I just follow this guide: https://github.com/react-native-community/react-native-webview/blob/master/docs/Guide.md#communicating-between-js-and-native and succeeded in my work. Here is solution:
1. Define script to send document height to native env after loaded website.
2. Handle onMesssage of webview component and reset Height via state.
const webViewScript = `
setTimeout(function() {
window.postMessage(document.documentElement.scrollHeight);
}, 500);
true; // note: this is required, or you'll sometimes get silent failures
`;
...
constructor(props) {
super(props);
this.state = {
webheight:100,
}
...
<WebView style={{height: this.state.webheight}}
automaticallyAdjustContentInsets={false}
scrollEnabled={false}
source={{uri: "http://<your url>"}}
onMessage={event => {
this.setState({webheight: parseInt(event.nativeEvent.data)});
}}
javaScriptEnabled={true}
injectedJavaScript ={webViewScript}
domStorageEnabled={true}
></WebView>
Hope that help!
A reliable implementation of this behavior is with useAutoheight hook from #formidable-webview/webshell library.
The latter allows to inject "features" into WebViews, e.g. scripts and behaviors.
In this example, we will use 3 features + the aforementioned hook:
HandleHTMLDimensionsFeature which is required by useAutoheight hook to get document size updates;
ForceResponsiveViewportFeature to work around mobile virtual viewport;
ForceElementSizeFeature to work around cyclic size constraints
This component should work with any webpage.
import React from 'react';
import makeWebshell, {
HandleHTMLDimensionsFeature,
ForceResponsiveViewportFeature,
ForceElementSizeFeature,
useAutoheight
} from '#formidable-webview/webshell';
import WebView from 'react-native-webview';
const Webshell = makeWebshell(
WebView,
new HandleHTMLDimensionsFeature(),
new ForceResponsiveViewportFeature({ maxScale: 1 }),
new ForceElementSizeFeature({
target: 'body',
heightValue: 'auto',
widthValue: 'auto'
})
);
export default function ResilientAutoheightWebView(props) {
const { autoheightWebshellProps } = useAutoheight({
webshellProps: props
});
return <Webshell {...autoheightWebshellProps} />;
}
More resources:
Try this on Expo
Full guide here.
Using postMessage and onMessage like below worked for me perfectly.
Credit to iamdhj
onWebViewMessage = (event: WebViewMessageEvent) => {
this.setState({webViewHeight: Number(event.nativeEvent.data)})
}
render() {
return (
<ScrollView>
<WebView
style={{ height: this.state.webViewHeight }}
source={{html: '...'}}
onMessage={this.onWebViewMessage}
injectedJavaScript='window.ReactNativeWebView.postMessage(document.body.scrollHeight)'
/>
</ScrollView>
)
}
The WebView has default styles. If you want to set height, you also need to add flex: 0, as stated in the documentation:
Please note that there are default styles (example: you need to add flex: 0 to the style if you want to use height property).
I made a little component to make this functionality reusable if it helps anyone!
import React, { useState } from "react";
import WebView from "react-native-webview";
const DynamicHeightWebView = (props) => {
const [height, setHeight] = useState(0);
const webViewScript = `
setTimeout(function() {
window.ReactNativeWebView.postMessage(document.documentElement.scrollHeight);
}, 500);
true; // note: this is required, or you'll sometimes get silent failures
`;
return <WebView
{...props}
style={{
...props.style,
height: height,
}}
automaticallyAdjustContentInsets={false}
scrollEnabled={false}
onMessage={event => {
setHeight(parseInt(event.nativeEvent.data));
}}
javaScriptEnabled={true}
injectedJavaScript ={webViewScript}
domStorageEnabled={true}
useWebKit={true}
/>
}
export default DynamicHeightWebView;
Apparently the problem was I had javaScriptEnabled={false}.
After enabling it everything worked.
I waste whole day to fix the height issue but in the end I had to shift to another library
This one is easy and good
https://github.com/archriss/react-native-render-html
You can get the content height by injecting the JS code as suggested by #ken-ratanachai-s. Although, You will experience certain irregularities in some devices (Extra height after the content). This is becuase the javascript returns the content height in pixels, but we need to use display points in react native. To fix this, Divide the height from javascript with the pixel ratio as follows.
import { WebView, PixelRatio } from 'react-native'
const [webviewHeight, setWebviewHeight] = useState(0)
const onProductDetailsWebViewMessage = event => {
setWebviewHeight(Number(event.nativeEvent.data)/PixelRatio.get())
}
return <WebView
originWhitelist={['*']}
style={{ height: productDetailsWebviewHeight }}
onMessage={onProductDetailsWebViewMessage}
injectedJavaScript='window.ReactNativeWebView.postMessage(document.body.scrollHeight)'
source={{ html: "..." }}
/>
Pixel ratio ref.: https://reactnative.dev/docs/pixelratio
Courtesy: https://stackoverflow.com/a/65976827/5321660
use package react-native-autoheight-webview
I recommend react-native-autoheight-webview.
it perfect work for me.
https://github.com/iou90/react-native-autoheight-webview
UPDATE:
Best answer is #Ken Ratanachai S.'s answer.
https://stackoverflow.com/a/65976827/9757656
I am trying to create a simple and standard chat window just like WhatsApp, Telegram, etc. Where when the inputbox on the bottom of the screen got focus, the keyboard appear and the inputbox goes right about the keyboard like this...
This is my code...
import React from 'react'
import {Actions} from 'react-native-router-flux'
import {ScrollView, View, TextInput, Text} from 'react-native'
import style from './style'
class Chat extends React.Component {
componentDidMount() {
Actions.refresh({title: 'Chat'})
}
render() {
return (
<View style={{flex:1}}>
<ScrollView>
<View style={{flex: 1}}>
<Text>Hello !</Text>
</View>
</ScrollView>
<View style={{borderWidth: 1, padding:15}}>
<TextInput/>
</View>
</View>
)
}
}
export default Chat
The result is this very simple one:
But when my inputbox got focus, the inputbox still got stucked in the bottom of the screen, behind the keyboard. Any suggestion for this ?
One way would be to change your input's position when keyboard shows and hides.
You need to add two listeners for when keyboard shows and hides:
import { Keyboard } from 'react-native';
constructor(props) {
super(props);
this.state = {
keyboardHeight: 0,
inputHeight: 40
}
}
componentDidMount() {
Keyboard.addListener('keyboardDidShow', this._keyboardDidShow.bind(this));
Keyboard.addListener('keyboardDidHide', this._keyboardDidHide.bind(this));
}
_keyboardDidShow(e) {
this.setState({keyboardHeight: e.endCoordinates.height});
}
_keyboardDidHide(e) {
this.setState({keyboardHeight: 0});
}
render() {
return (
<TextInput style={{marginBottom: keyboardHeight + inputHeight}} />
)
}
You can also add some animation to make it move smoothly.
Another Suggestion would be :
React-Native-Keyboard-Aware-Scroll-View
This is great when you have multiples text inputs and does handle some animation. Not perfect but did the job for me.
I'm following this Udemy react-native course and in one of the examples he uses a picker to select data in the screen. Back when he tried it it seems like it was working but now I get a weird result when I try to render it.
If I follow his code exactly the picker shows after all the other items, after making some changes I get it to show kind of at the right place but it is now squished, which is still not correct:
I am definitely doing something wrong here in terms of how to render it, here's the code (full example on github):
import React from 'react';
import {Picker, Text, StyleSheet, View} from 'react-native';
import {connect} from 'react-redux';
import {Card, CardSection, Input, Button} from "./common";
import {employeeUpdate} from "../actions";
class EmployeeCreate extends React.Component {
updateEmployee(name, value) {
this.props.employeeUpdate({prop: name, value: value})
}
renderPickerItems() {
return ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']
.map((item) => <Picker.Item key={item} label={item} value={item}/>);
}
render() {
return (
<Card>
<CardSection>
<Input
label="Name"
placeholder="Your Name"
value={this.props.name}
onChangeText={this.updateEmployee.bind(this, 'name')}
/>
</CardSection>
<CardSection>
<Input
label="Phone"
placeholder="555-555-5555"
keyboardType="phone-pad"
value={this.props.phone}
onChangeText={this.updateEmployee.bind(this, 'phone')}
/>
</CardSection>
<CardSection style={{flex: 1}}>
<View style={styles.shiftContainerStyle}>
<Text style={styles.pickerTextStyle}>Shift</Text>
<Picker
style={styles.pickerStyle}
selectedValue={this.props.shift}
onValueChange={this.updateEmployee.bind(this, 'shift')}
>
{this.renderPickerItems()}
</Picker>
</View>
</CardSection>
<CardSection>
<Button>
Create
</Button>
</CardSection>
</Card>
);
}
}
const styles = StyleSheet.create({
pickerTextStyle: {
fontSize: 18,
lineHeight: 23,
flex: 1,
},
pickerStyle: {
flex: 2,
},
shiftContainerStyle: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
}
});
const mapStateToProps = state => {
const {name, phone, shift} = state.employeeForm;
return {
name,
phone,
shift,
};
};
export default connect(mapStateToProps, {employeeUpdate})(EmployeeCreate);
Any idea what I could do to render this correctly?
You need to remove style={{flex: 1}} from this line in your code:
<CardSection style={{flex: 1}}>
The reason being that your parent container, Card, doesn't have any flex or width/height values defined. If flex is left undefined, the default is flex: 0. If you look at the docs for flex, you'll see that:
When flex is 0, the component is sized according to width and height and it is inflexible.
Combine that with having no width/height defined and you get this behavior on rendering your CardSections:
The three CardSections (input, input, button) will take up the default width and height based on their children. That is the default styling for the Inputs and Button.
The CardSection with style={{flex: 1}} will calculate its width and height based on the remaining space taken up by the parent container(s) per the definition of flex: 1:
When flex is a positive number, it makes the component flexible and it will be sized proportional to its flex value. So a component with flex set to 2 will take twice the space as a component with flex set to 1.
The parent container, Card, in this case has no extra space left. So what happens is that this CardSection ends up with 0 height. Hence the strange overflow rendering you're seeing.
Once you remove style={{flex: 1}}, the width and height of the CardSection will be defined by it's child components which, like Input and Button, do have a styles and default styles.
Whether or not this is correct behavior per the Yoga spec (Yoga is what React Native uses for layout) is up for debate and has tripped up others before. Definitely look over that first StackOverflow answer I linked to as it has far more detail and explanation on gotchas than any of the documentation on React Native wrt flex.
I currently have a KeyboardAvoidingView with a hard-coded keyboardVerticalOffset of 64. This works fine on the iPhone but is about 20px short on the iPhone X.
The component looks like this:
<KeyboardAvoidingView behavior='padding' keyboardVerticalOffset={ 64 }>
<View style={ styles.messageList }>
...
</View>
<View style={ styles.messageInput }>
...
</View>
</KeyboardAvoidingView>
Is there a better way to determine what keyboardVerticalOffset should be than hard coding a value? Is there something else I could be doing differently with component placement? I'm open to any suggestions.
iPhone 8
iPhone X
This is caused by the status bar height being different for iphoneX. (you also get the same issue with other iphones if you toggle the 'in-call' status bar using ⌘Y in the simulator).
You can get the status bar height and use this to set the keyboardVerticalOffset value of the KeyboardAvoidingView. (in our case this was 44 + statusBarHeight)
import React, {Component} from 'react';
import {KeyboardAvoidingView, NativeModules, StatusBarIOS} from 'react-native';
const {StatusBarManager} = NativeModules;
export class IOSKeyboardAvoidingView extends Component {
state = {statusBarHeight: 0};
componentDidMount() {
StatusBarManager.getHeight((statusBarFrameData) => {
this.setState({statusBarHeight: statusBarFrameData.height});
});
this.statusBarListener = StatusBarIOS.addListener('statusBarFrameWillChange', (statusBarData) => {
this.setState({statusBarHeight: statusBarData.frame.height});
});
}
componentWillUnmount() {
this.statusBarListener.remove();
}
render() {
const {style, children} = this.props;
return (
<KeyboardAvoidingView
behavior="padding"
keyboardVerticalOffset={44 + this.state.statusBarHeight}
style={style}
>{children}
</KeyboardAvoidingView>
);
}
}
Please refer to : https://stackoverflow.com/a/51169574/10031014 for similar issues
I have used a custom component to overcome this situation.
import React from "react";
import {Animated, Keyboard} from "react-native";
export default class KeyboardAwareComponent extends React.Component {
constructor(props) {
super(props)
this.keyboardHeight = new Animated.Value(0);
}
componentWillMount () {
this.keyboardWillShowSub = Keyboard.addListener('keyboardWillShow', this.keyboardWillShow);
this.keyboardWillHideSub = Keyboard.addListener('keyboardWillHide', this.keyboardWillHide);
}
componentWillUnmount() {
this.keyboardWillShowSub.remove();
this.keyboardWillHideSub.remove();
}
keyboardWillShow = (event) => {
Animated.parallel([
Animated.timing(this.keyboardHeight, {
duration: event.duration,
toValue: event.endCoordinates.height,
})
]).start();
};
keyboardWillHide = (event) => {
Animated.parallel([
Animated.timing(this.keyboardHeight, {
duration: event.duration,
toValue: 0,
})
]).start();
};
render(){
const {children, style, ...props} = this.props
return(
<Animated.View style={[{flex:1,alignItems:'center',paddingBottom: this.keyboardHeight},style]} {...props}>
{children}
</Animated.View>
);
}
}
Just use the component "KeyboardAwareComponent" as a root component of any page. It will automatically adjust the view when keyboard will show or hide.
Example:---
YourComponent extends React.Component{
render(){
<KeyboardAwareComponent>
{Your child views}
</KeyboardAwareComponent>
}
}
So I did a quick check, given my understanding of how to do this in native iOS, and it seems like in newer versions of react native, you can do this relatively easily.
There do seem to be a couple of options, depending on your flexibility needs.
First, have you tried using KeyboardAvoidView instead of a standard container View without specifying keyboardVerticalOffset?
Another option that gives you much more control (similar to what I would do in a native iOS app) is to use the Keyboard module to create listeners on the keyboard events.
componentWillMount () {
this.keyboardWillShowSub = Keyboard.addListener('keyboardWillShow', this.keyboardWillShow);
this.keyboardWillChangeSub = Keyboard.addListener('keyboardWillChangeFrame', this.keyboardWillChange);
this.keyboardWillHideSub = Keyboard.addListener('keyboardWillHide', this.keyboardWillHide);
}
componentWillUnmount() {
this.keyboardWillShowSub.remove();
this.keyboardWillChangeSub.remove();
this.keyboardWillHideSub.remove();
}
This would allow you to get the keyboard height from the event parameter:
keyboardWillShow = (event) => {
Animated.parallel([
Animated.timing(this.keyboardHeight, {
duration: event.duration,
toValue: event.endCoordinates.height,
}),
Animated.timing(this.imageHeight, {
duration: event.duration,
toValue: IMAGE_HEIGHT_SMALL,
}),
]).start();
};
Repeat something similar for keyboardWillChange and keyboardWillHide.
For a better, more detailed explanation of your options, see this page:
https://medium.freecodecamp.org/how-to-make-your-react-native-app-respond-gracefully-when-the-keyboard-pops-up-7442c1535580
I think the best first test would be to try to remove the keyboardVerticalOffset before trying to add code to handle the keboard events.
Hi I know it's a known issue about the auto height of webview in react native,
and I have tried all the possibles solutions I've found on the internet such as :
https://gist.github.com/epeli/10c77c1710dd137a1335
https://github.com/danrigsby/react-native-web-container/blob/master/index.js
and all the solutions suggested in:
React native: Is it possible to have the height of a html content in a webview?
But unfortunately none of these seems to work for me,
I understand that the workaround they all suggest is to set the title to the height, but in my case it seems that the title always stays the same which is :
"text/html ...." and the rest of my html.
I get the html content from an API, it comes without a body, head or html tags, I've also tried adding these tags manually to the html and nothing seems to work.
I would love to hear if anyone else had that problem and how did it get fixed.
I wrap WebView inside a View, and set the height from the View.
<View style={{ height: 200 }}>
<WebView
automaticallyAdjustContentInsets={false}
source={{uri: 'https://player.vimeo.com/video/24156534?title=0&byline=0&portrait=0'}}
/>
</View>
I just follow this guide: https://github.com/react-native-community/react-native-webview/blob/master/docs/Guide.md#communicating-between-js-and-native and succeeded in my work. Here is solution:
1. Define script to send document height to native env after loaded website.
2. Handle onMesssage of webview component and reset Height via state.
const webViewScript = `
setTimeout(function() {
window.postMessage(document.documentElement.scrollHeight);
}, 500);
true; // note: this is required, or you'll sometimes get silent failures
`;
...
constructor(props) {
super(props);
this.state = {
webheight:100,
}
...
<WebView style={{height: this.state.webheight}}
automaticallyAdjustContentInsets={false}
scrollEnabled={false}
source={{uri: "http://<your url>"}}
onMessage={event => {
this.setState({webheight: parseInt(event.nativeEvent.data)});
}}
javaScriptEnabled={true}
injectedJavaScript ={webViewScript}
domStorageEnabled={true}
></WebView>
Hope that help!
A reliable implementation of this behavior is with useAutoheight hook from #formidable-webview/webshell library.
The latter allows to inject "features" into WebViews, e.g. scripts and behaviors.
In this example, we will use 3 features + the aforementioned hook:
HandleHTMLDimensionsFeature which is required by useAutoheight hook to get document size updates;
ForceResponsiveViewportFeature to work around mobile virtual viewport;
ForceElementSizeFeature to work around cyclic size constraints
This component should work with any webpage.
import React from 'react';
import makeWebshell, {
HandleHTMLDimensionsFeature,
ForceResponsiveViewportFeature,
ForceElementSizeFeature,
useAutoheight
} from '#formidable-webview/webshell';
import WebView from 'react-native-webview';
const Webshell = makeWebshell(
WebView,
new HandleHTMLDimensionsFeature(),
new ForceResponsiveViewportFeature({ maxScale: 1 }),
new ForceElementSizeFeature({
target: 'body',
heightValue: 'auto',
widthValue: 'auto'
})
);
export default function ResilientAutoheightWebView(props) {
const { autoheightWebshellProps } = useAutoheight({
webshellProps: props
});
return <Webshell {...autoheightWebshellProps} />;
}
More resources:
Try this on Expo
Full guide here.
Using postMessage and onMessage like below worked for me perfectly.
Credit to iamdhj
onWebViewMessage = (event: WebViewMessageEvent) => {
this.setState({webViewHeight: Number(event.nativeEvent.data)})
}
render() {
return (
<ScrollView>
<WebView
style={{ height: this.state.webViewHeight }}
source={{html: '...'}}
onMessage={this.onWebViewMessage}
injectedJavaScript='window.ReactNativeWebView.postMessage(document.body.scrollHeight)'
/>
</ScrollView>
)
}
The WebView has default styles. If you want to set height, you also need to add flex: 0, as stated in the documentation:
Please note that there are default styles (example: you need to add flex: 0 to the style if you want to use height property).
I made a little component to make this functionality reusable if it helps anyone!
import React, { useState } from "react";
import WebView from "react-native-webview";
const DynamicHeightWebView = (props) => {
const [height, setHeight] = useState(0);
const webViewScript = `
setTimeout(function() {
window.ReactNativeWebView.postMessage(document.documentElement.scrollHeight);
}, 500);
true; // note: this is required, or you'll sometimes get silent failures
`;
return <WebView
{...props}
style={{
...props.style,
height: height,
}}
automaticallyAdjustContentInsets={false}
scrollEnabled={false}
onMessage={event => {
setHeight(parseInt(event.nativeEvent.data));
}}
javaScriptEnabled={true}
injectedJavaScript ={webViewScript}
domStorageEnabled={true}
useWebKit={true}
/>
}
export default DynamicHeightWebView;
Apparently the problem was I had javaScriptEnabled={false}.
After enabling it everything worked.
I waste whole day to fix the height issue but in the end I had to shift to another library
This one is easy and good
https://github.com/archriss/react-native-render-html
You can get the content height by injecting the JS code as suggested by #ken-ratanachai-s. Although, You will experience certain irregularities in some devices (Extra height after the content). This is becuase the javascript returns the content height in pixels, but we need to use display points in react native. To fix this, Divide the height from javascript with the pixel ratio as follows.
import { WebView, PixelRatio } from 'react-native'
const [webviewHeight, setWebviewHeight] = useState(0)
const onProductDetailsWebViewMessage = event => {
setWebviewHeight(Number(event.nativeEvent.data)/PixelRatio.get())
}
return <WebView
originWhitelist={['*']}
style={{ height: productDetailsWebviewHeight }}
onMessage={onProductDetailsWebViewMessage}
injectedJavaScript='window.ReactNativeWebView.postMessage(document.body.scrollHeight)'
source={{ html: "..." }}
/>
Pixel ratio ref.: https://reactnative.dev/docs/pixelratio
Courtesy: https://stackoverflow.com/a/65976827/5321660
use package react-native-autoheight-webview
I recommend react-native-autoheight-webview.
it perfect work for me.
https://github.com/iou90/react-native-autoheight-webview
UPDATE:
Best answer is #Ken Ratanachai S.'s answer.
https://stackoverflow.com/a/65976827/9757656