react-native-webview avoid keyboard (iOS) - react-native

The react-native-webview has in my experience proved difficult to behave as I want around the keyboard on iOS. It doesn't automatically change it's height when the keyboard is shown and its contents gets concealed by the keyboard.
It also behaves strangely wrapped with the KeyboardAvoidingView. In my case it seems to adjust the content of the WebView too much, approximately twice the height of the keyboard. This same behavior appeared when I manually listened for the keyboard open/close events and adjusted the height of the WebView accordingly:
componentDidMount(){
Keyboard.addListener("keyboardWillShow", this.keyboardDidShow.bind(this));
Keyboard.addListener("keyboardWillHide", this.keyboardDidHide.bind(this));
}
componentWillUnmount(){
Keyboard.removeListener("keyboardWillShow", this.keyboardDidShow.bind(this));
Keyboard.removeListener("keyboardWillHide", this.keyboardDidHide.bind(this));
}
keyboardDidShow(event){
this.setState({
keyboardHeight: event.endCoordinates.height
});
}
keyboardDidHide(event){
this.setState({
keyboardHeight: 0
});
}
render(){
return (
<WebView
style={{flex: 1, maxHeight: Dimensions.get("window").height - this.state.keyboardHeight}}
/>
);
}
I've found a solution, not optimal, but a solution non the less. My answer is posted below.

As I couldn't find any discussions on this particular behavior and no solutions that worked for me, I worked my way through the props of the react-native-webview docs. What finally worked for me in version 0.59.9 of React Native and version 5.11.0 of React Native WebView was as described above, manually setting the height of the WebView in the keyboard event listeners and setting the WebView prop useWebKit={false}.
Unfortunately this means that on the native side of the WebView, it's now using UIWebView which is deprecated and will in a future release be deleted.
Either way, this is what I'm rolling with and simply wanted to share my findings in case anyone finds themselves with the same issue.

Related

Janky translateY on Reanimated View inside of ScrollView as user scrolls

I am using react-native-reanimated v1. I want to make a <Reanimated.View> appear as if it is fixed within the scroll view by using translateY. GIF of how of it should behave is at very end of post. I have simplified the code in the snippet below, and I have the full working code in the snack.
https://snack.expo.io/#noitidart/reanimated-scroll-view
As you scroll, you notice the position of the view is not staying fixed at the top. I attached a video taken on iOS of the snack.
If I add scrollEventThrottle={16} it fixes the issue on iOS, but on Android if you scroll even a little faster than normal you see the transform is lagging. I think there should be a way on iOS without the scrollEventThrottle property too, it doesn't make sense to me that we need this as reanimated is supposed to update every frame.
Any ideas on how to fix this?
const ReanimatedScrollView = Reanimated.createAnimatedComponent(ScrollView);
export default function App() {
const translateY = Reanimated.useValue(0);
const handleScroll = Reanimated.event([
{
nativeEvent: nativeEvent => Reanimated.block([Reanimated.set(translateY, nativeEvent.contentOffset.y)])
}])
return (
<ReanimatedScrollView onScroll={handleScroll}>
<Reanimated.View style={{ transform: [{ translateY }] }} />
</ReanimatedScrollView>
);
}
Janky on iOS
Janky on Android (with and without scrollEventThrottle={16})
Here is video of how it should be (with scrollEventThrottle={16} on iOS, but it doesn't fix up Android)

React Native Dimensions not working on rotation

I am running the code posted (for Class Component) at:
https://reactnative.dev/docs/dimensions
Yet, at the Android Studio simulator, If I "rotate" the device the dimension's values do not change i.e. I was expecting that the height and width could change with the rotation but this is not happening. Any ideas why?
According to the info posted at the previous link it says:
Although dimensions are available immediately, they may change (e.g due to device rotation, foldable devices etc) so any rendering logic or styles that depend on these constants should try to call this function on every render, rather than caching the value (for example, using inline styles rather than setting a value in a StyleSheet).
I also tried using using useWindowDimensions but this is limited to functional react components.
So far, I can not find a reliable way to detect if an Android device rotated using React-Native and class components :-(
Note: This problem also happens with React Function Component
Try: https://reactnative.dev/docs/usewindowdimensions
It works for me
import { useWindowDimensions } from 'react-native';
const { height, width } = useWindowDimensions();
I had the same problem. Upgrading react-native to version 0.64.2 fixed it.
My problem on 0.61.5 was not that the values were not correct, but that the view was not re-rendered at all after the rotation, so I added this to the constructor() and it worked:
Dimensions.addEventListener('change', () => {
this.setState({});
});

How to solve React Native Scroll Animation issue during slow scroll?

I have made an example code here with Snack expo
Animated Header
The issue that I'm having is that my animation is not smooth enough.
It looks like it's shaking.
Demo video YouTube Video
I can't seem to find what's the issue here and also tried to fiddle around with the scrollEventThrottle, alwaysBounceVertical, bounces, bouncesZoom props in ScrollView.
I figure out what the problem is and the issue is not because of the performance.
The problem is because of the styling on the header.
Adding the position to absolute will solve this problem.
But there's another issue that appeared when having the position as absolute, the component inside the header such as TextInput won't appear when a touch event occurs.
To solve this new issue, you have to add the zIndex.
More tutorial about zIndex
Animated Header Fixed
Try adding useNativeDriver:
onScroll={
Animated.event([
{
nativeEvent: {
contentOffset: {
y: scrollY,
},
},
},
],
{ useNativeDriver: true })
}
But I think in React Native, ScrollView is not supposed to work with extremely long duplicated content. I suggest you to use a flatlist for your use case.
Adding removeClippedSubviews = {true} on top most ScrollView solved my issue and app performance feels so light.

react-native - TouchableHighlight: Remove highlighting after onPress?

I am developing a simple react-native app and am encountering an issue on TouchableHighlight:
When pressing the TouchableHighlight, a new screen is displayed (using StackNavigator from react-navigation). After pressing the back-button and returning to the original screen, the TouchableHighlight still has a black background-color - meaning, that it is still highlighted.
My questions are:
Is there a way to manually deactivate the highlighting of a TouchableHighlight-component? That way I could disable the highlighting after onPress has run.
What could be possible reasons to why the TouchableHighlight stays highlighted? I am using it on other parts of my app without navigation, and I could imagine that it has to do with that.
The TouchableHighlight exists within a FlatList. The renderItems-method looks like the following:
let handlePress = () => {
this.props.navigation.navigate('DetailsScreen');
};
return <TouchableHighlight
onPress={handlePress}>
<Text>Some Text</Text>
</TouchableHighlight>;
If you need/want any further information, please let me know. I've tested the code on android, using the Genymotion-emulator with Marshmallow.
Versions are:
node -v: 8.9.4
npm -v: 5.6.0
react-native-cli: 2.0.1
react-native: 0.54.2
react-navigation: 1.5.2
Build environment: Windows 10 64-bit
At this point, I'm quite certain that the error is somewhere in my code, as TouchableHighlight works correctly on other parts of my app, and it propably has to do with the navigation-call, but I was unable to pinpoint, why exactly. I've made sure that there are no exceptions or anything like that in my app, and that the onPress-method therefore finishes successfully.
You can replace Touchable Highlight with Touchable opacity and simply set activeOpactity prop with value 1. It will not highlight the press.
<TouchableOpacity activeOpacity={1}>....</TouchableOpacity>
After using the tip from #Kartiikeya and exchanging TouchableHighlight with TouchableOpacity and back to TouchableHighlight, it now works as expected:
Now, after onPress has been executed, the button (be it a TouchableOpacity or a TouchableHighlight) looses its effect.
I am not sure, why it works now. The obvious reason would be, that a recompilation of the source code fixed errors - but I recompiled it for writing the original question multiple times before, so that that cannot be an option. Other users I would suggest to clear any cache possible, and especially do the following steps:
Close and reopen the android emulator / restart your testing device
Restart the build PC
Recompile all source code
Check in your console for errors and/or exceptions (obviously)
Replace TouchableHighlight with TouchableOpacity, recompile, check if the error still exists - and if not, reexchange TouchableOpacity to TouchableHighlight
You can replace Touchable Highlight with Touchable opacity. It won't highlight the press.
return <TouchableOpacity
onPress={handlePress}>
<Text>Some Text</Text>
</TouchableOpacity >;
For me, i needed to disable the highlight effect after onLongPress has been fired. You can simply change the key of the touchable using a re-render when you want to remove it.
Here's an example:
<TouchableHighLight
onPress={this.pressRow}
style={styles.outerContainer}
onLongPress={() => this.setState({ onLongPressed: true })}
onPressOut={() => this.setState({ onLongPressed: false })}
key={`long-pressed-${this.state.onLongPressed ? 'yes' : 'no'}`}>
<View style={styles.innerContainer}>
{rowText}
{rowIcon}
</View>
</TouchableHighLight>
Following Leonardo Lusoli's answer, there one thing you should also add is
useEffect(() => {
if(isLongPressed){
setIsLongPressed(false)
}
}, [isLongPressed])
This step is necessary because
when the first onLongPress event is fired it will set isLongPressed to true and thus changing the key the component is re-rendered and is identifies as a new component and previour event listners are discareded so the onPressOut will not be fired. So
when isLongPressed is set to true the component re-renders and then immediatietly we set it's value to false and thus we get the expected behaviour. Otherwise we will get the unexpected behaviour followed by one expected behaviour.

React native detect screen rotation

I'm using onLayout to detect screen orientation and it's working fine inside my root view, but when I implemented inside the drawer it didn't work, any reason why this happens ?
code :
import Drawer from 'react-native-drawer'
...
onLayout(e) {
console.log('onLayout');
}
<Drawer onLayout={this.onLayout}
It didn't log any thing when orientation changed!
This is because the Drawer component doesn't take onLayout as a prop. You can see in the source code that the rendered View does use onLayout, but it's not pulling from something like this.props.onLayout.
I'm not exactly sure what you're looking to do, but maybe this issue will help you. As it shows, you can pass a function into openDrawerOffset instead of an integer or a ratio in order to be a little more dynamic with how you set your offset:
openDrawerOffset={(viewport) => {
if (viewport.width < 400) {
return viewport.width * 0.1;
}
return viewport.width - 400;
}}
You might also benefit from the Event handlers that react-native-drawer has to offer.