Set contentInset and contentOffset in React Native's ScrollView - javascript

I am trying to make my content start 100 px from the top in React Native. I have tried with
const OFFSET = 100
const ScrollViewTest = (props) => (
<ScrollView
contentInset={{ top: OFFSET }}
contentOffset={{ y: OFFSET }}
>
<Text>Test</Text>
</ScrollView>
)
But when I render the screen, it starts from 0 px, but if I scroll a little, it will scroll to 100px from the top and stay there.
So it seems React Native doen't trigger the contentOffset and contentInset properties on initialization.
How can I fix this? I have also tried setting automaticallyAdjustContentInsets={false} with no changes.
Also, it seems these properties are for iOS only. Are there any similar properties for Android?

You should use the contentContainerStyle1 property with a marginTop on your ScrollView.
By using this property it will apply the container wrapping your children (which I believe is what you want in this case) and with the additional benefit of working on both iOS and Android.
I.e.
const OFFSET = 100
const ScrollViewTest = (props) => (
<ScrollView
contentContainerStyle={{ marginTop: OFFSET }}
>
<Text>Test</Text>
</ScrollView>
)

componentDidMount(){
if(Platform.OS==='ios'){
this.refs._scrollView.scrollToOffset({animated:false,offset:-OFFSET});
}
}
render(){
return(
<ScrollView
ref="_scrollView"
contentInset={{top: OFFSET}}
...>
...
</ScrollView>
)
}

When I try to call scrollTo from componentDidMount method it does not scroll. I have to use workaround with setTimeout to make it work:
componentDidMount() {
setTimeout(() => this.refs._scrollView.scrollTo({ x: 100, y: 0 }) , 0);
}

As I wrote in the comments to Peter Theill's solution, the proposed solutions here do not work in 100% of the cases, e.g. for one of the following scenarios:
You have a transparent Header where you want the content to scroll underneath (e.g. a blurred header).
You could solve this with Peter Theill's solution and replacing contentContainerStyle with style={{paddingTop: headerHeight}}.
But this won't help you if you have also the following typical scenario:
You want to use RefreshControl. It will be displayed behind the header and there is no way to position it differently on iOS. Therefore, you need to use contentOffset & contentInset as Jamgreen proposed. However, I had kind of similar issues with initial rendering of offset. I solved it in my case with the prop automaticallyAdjustContentInsets={false}, so I would recommend to try this!
Here is the complete solution (for android & iOS) in my scenario:
<ScrollView
style={{paddingTop: Platform.select({android: headerHeight, ios: 0})}}
scrollIndicatorInsets={{ right: 1 }}
ref={scrollViewRef}
contentInset={{ top: headerHeight}}
contentOffset={{ x: 0, y: Platform.select({android: 0, ios: -headerHeight})}}
automaticallyAdjustContentInsets={false}
refreshControl={<RefreshControl
refreshing={refresh}
onRefresh={() => {
setRefresh(true);
setTimeout(() => setRefresh(false), 10000);
}}
colors={[inputPlaceholderGray]}
tintColor={inputPlaceholderGray}
progressViewOffset={headerHeight}
/>}
>
{CONTENT}
</ScrollView>
Hope it still helps people although the issue is already old.

padding-like effect:
const OFFSET = 100
const ScrollViewTest = (props) => (
<ScrollView>
<View style={{ height: OFFSET }} />
<Text>Test</Text>
</ScrollView>
)
header-like effect:
const OFFSET = 100
const ScrollViewTest = (props) => (
<View style={{ paddingTop: OFFSET }}>
<ScrollView>
<Text>Test</Text>
</ScrollView>
</View >
)

Related

React Native FlatList refreshing not working

i got an problem with the refreshing on pull function. The FlatList renders fine, but pull to refresh is not working. This is my current sourcecode:
return (
<View style={GlobalStyles.flex1}>
<FlatList
showsVerticalScrollIndicator={false}
refreshControl={
<RefreshControl
refreshing={isRefreshing}
onRefresh={() => {
console.log("onRefresh loadVocable");
loadVocables();
}}
/>
}
data={vocables}
keyExtractor={vocable => vocable.id}
onEndReached={() => {
if (!isRefreshing && !endReached) {
loadVocables();
}
}}
renderItem={vocable => (
<TouchableOpacity
onPress={() => {
props.navigation.navigate({ routeName: "editVocable", params: { vocable: vocable.item } });
}}
onLongPress={() => {
handleLongPress(vocable.item.id);
}}>
<Card style={styles.cardStyle}>
<View style={styles.part1}>
<Text style={styles.vocableText}>{vocable.item.wordENG}</Text>
<Text style={styles.vocableText}>{vocable.item.wordDE}</Text>
</View>
<View style={styles.part2}>
<Ionicons name={vocable.item.known ? "md-checkmark-circle" : "md-close-circle"} size={23} color={vocable.item.known ? Colors.success : Colors.danger} />
</View>
</Card>
</TouchableOpacity>
)}
/>
</View>
);
In the official docs is an example that says contentContainerStyle needs to be flex: 1 to know the height, that makes sence to me, so when i set contentContainerStyle with flex 1, refresh on pull works fine, but then i can't scroll anymore in the Flatlist and everthing get very tight, so the style also change then. Does anyone know why this happen?
The first picture is with "contentContainerStyle={{flex: 1}}" and the last one is without contentContainerStyle.
The answer was so easy, I compared a new project (there worked my code) to the one where the problem was and after 5 days I found the little error:
My import was wrong!
I imported FlatList like this:
import { FlatList } from "react-native-gesture-handler";
But it needs to get imported from React-Native so like this:
import { FlatList } from "react-native";
Thanks to #The1993, without the hint to compare the projects, maybe I would stuck forever on this error :D In the future I will compare working files to find any error!
contentContainerStyle is used to style inner content e.g items alignments, padding, etc
style is used to align its height and relations
You can replace style={{flex: 1}} instead of contentContainerStyle or wrap the parent element with flex: 1

Images in FlatList not displayed

I am using react native and firebase to create an application that's somewhat similar to a social network. In the profile screen I am trying to display the images that the user posted in a FlatList that has 3 columns. My problem is, when trying to display the images so that they occupy the full width of the FlatList, they don't seem to get displayed. However, when I set a fixed height, they do. I would like to make it so that they are shown with a 1/1 aspect ratio without having to fix a height.
This is my code:
<View style={styles.galleryContainer}>
<FlatList
numColumns={3}
horizontal={false}
data={posts}
renderItem={({ item }) => (
<View style={styles.imageContainer}>
<Image
style={styles.image}
source={{ uri: item.downloadURL }}
/>
</View>
)}
/>
</View>
And the styles:
galleryContainer: {
flex: 1
},
image: {
flex: 1,
aspectRatio: 1/1,
height: 120
},
imageContainer: {
flex: 1 / 3
}
I have tried setting the height to 100% and that did not work either, any ideas on what might be causing this?
Edit: I solved it by using useWindowDimensions and fetching the window size and dividing it by 3. This is the codethat I used to set the style:
const imageWidth = Math.floor(useWindowDimensions().width/3);
style={{width:imageWidth, aspectRatio:1/1, flex:1, height:imageWidth}}

React Native <Modal /> onDismiss not invoked

Based on the code below, I would expect to see 'onDismiss!' when I swipe down on the modal view.
When I swipe down on the modal view it does not invoke the provided function. I can't find any other React Native users experiencing this problem. I am using React Native version 0.60.6.
Am I using Modal the wrong way or is this a bug?
<Modal
animationType="slide"
presentationStyle="pageSheet"
visible={showSetup}
hardwareAccelerated
onDismiss={() => alert('onDismiss!')}
onRequestClose={() => alert('onRequestClose!')}
>
<SetupView closeView={() => this.willClose()} />
</Modal>
This issue on the React Native issue tracker accurately describes this issue: https://github.com/facebook/react-native/issues/26892
I hope it's fixed soon!
Hey there you should try something like that
This is the code for the component rendered in my modal:
<TouchableWithoutFeedback
onPressOut={() => {
this.refs.view_ref.measure((fx, fy, width, height, px, py) => {
if (py === Dimensions.get('window').height) {
this.props.hide(false);
}
});
}}
>
<View style={styles.view} ref={'view_ref'}>
<View style={styles.nav}>
<Text style={styles.nav_title}>New Currency</Text>
<TouchableOpacity
onPress={() => {
this.props.hide(false);
}}
>
<Icon
style={styles.nav_close}
name={'close'}
size={30}
color={mode === 'dark' ? 'white' : 'black'}
/>
</TouchableOpacity>
</View>
</View>
</TouchableWithoutFeedback>
When wrapping the view you want to render in a TouchableWithoutFeedback, you get the "onPressOut" functionality. Then with this portion of code:
onPressOut={() => {
this.refs.view_ref.measure((fx, fy, width, height, px, py) => {
if (py === Dimensions.get('window').height) {
this.props.hide(false);
}
});
}}
you basically tell the view to listen on touch events happening at the top of the modal, thus it will be also executed when you swipe to dismiss
I was inspired by this answer:
https://github.com/facebook/react-native/issues/26892#issuecomment-605516625
Enjoy 😊
This is a bug with React Native, but I have found a workaround that works for me. Basically I have a TouchableWithoutFeedback fill the entire Modal. Then I use the onPressOut prop, which receives an event from which you can measure the locationY. What I have found is that if locationY < 0, the swipe was successful in dismissing the Modal, and in that case you can call setModalVisible(false). Then, next time you want to re-show the Modal, it will work. Hope it helps!
<Modal animationType="slide" presentationStyle="pageSheet" visible={ modal }>
<TouchableWithoutFeedback onPressOut={(e) => {
if (e.nativeEvent.locationY < 0) {
handleModal(false)
}}
>
<View style={ styles.modalOuter }>
<View style={ styles.modalInner }>
<Text>Hi from Modal</Text>
</View>
</View>
</TouchableWithoutFeedback>
</Modal>
// ...
const styles = StyleSheet.create({
modalInner: {
backgroundColor: "white",
position: "absolute",
bottom: 0,
width: "100%",
height: 400
},
modalOuter: {
backgroundColor: "white",
height: "100%"
}
});

Scroll area too small with SectionList React Native

I am currently trying to use a <SectionList> inside a classic <View>, all my datas are formatted, the list displays correctly and my item's actions are working.
The issue is that when I am on the top of my SectionList, the area available to trigger the scroll is actually really small ( roughly 100 pixels from the top of the list ). However, once i scroll down a bit from that area, the whole list becomes scrollable and works as intended until I scroll back to the top.
My parent View has flex: 1 as well as my SectionList
Environment
Working environment : MacOS Sierra 10.13.3
Expo version : 23.0
React Native version : 0.50
React : 16.0
Using an IPhone 8 simulation
There's no issue on Android
Steps to Reproduce
Classic creation of a SectionList inside of a View
Expected Behavior
The scroll must be triggered from everywhere in the SectionList
Actual Behavior
When the SectionList is at the top, the scroll only triggers inside a small area ( around 100px from the top of the list )
The code of my SectionList :
<View style={{ flex: 1 }}>
<SectionList
style={styles.openSectionList} // flex: 1
scrollEnabled
stickySectionHeadersEnabled
sections={this.sections}
keyExtractor={item => item["#id"]}
removeClippedSubviews
renderSectionHeader={({ section }) => (
<TouchableHighlight
onPress={() => this.onSectionHeaderPressRef(section.index)}
activeOpacity={0.65}
underlayColor="rgba(0, 0, 0, 0.2)"
style={styles.sectionHeader}
>
<View style={styles.categoryContentContainer}>
<View style={styles.firstPartContent}>
<Text style={styles.categoryHeaderText}>
{section.title === "Autres"
? "Mes produits"
: section.title}{" "}
</Text>
{section.nbItems - section.nbItemsSelected === 0 ? (
<CheckBox
label=""
checked
checkboxStyle={styles.checkbox}
checkboxContainer={styles.checkboxContainer}
/>
) : (
<Text
style={[
styles.categoryHeaderText,
{ color: Colors.contrastColor },
]}
>
({section.nbItems - section.nbItemsSelected})
</Text>
)}
</View>
<Image
source={require("../../../assets/common/chevron.png")}
style={
section.index === this.state.currentCategoryOpen
? styles.categoryChevronOpen
: styles.categoryChevronClosed
}
/>
</View>
</TouchableHighlight>
)}
renderItem={({ item }) =>
this.state.currentCategoryOpen === item.categoryIndex ? (
<ShoppingListProduct
key={item["#id"]}
ingredient={item}
updateIngredient={this.updateIngredientListRef}
onLongPress={this.itemLongPressedRef}
/>
) : null}
/>
</View>
A GIF of the actual behavior ( I'm trying to scroll everytime the cursor is moving ) where we can see that the scroll only triggers when I am above a certain height.
GIF
Any help would be appreciated as I don't know if that's a bug and/or me implementing the component wrong.
Thank you by advance.
someone asked me the solution for this via email so I might as well add it here, from what i remember it was a position/imbrication problem with the components.
I can't remember exactly but I ended up with this code ( my page content changes so that's why it is set as a variable )
// render method
component = ( <SectionList
style={styles.openSectionList} // flex: 1, height: "100%"
scrollEnabled
stickySectionHeadersEnabled
sections={this.sections}
bounces={false}
keyExtractor={item =>
item["#id"] === undefined ? item.userIngredient : item["#id"]
}
getItemLayout={this.getItemLayout}
renderSectionHeader={this.renderSectionListHeaderRef}
renderItem={this.renderSectionListItemRef}
/> )
return (
<View style={{ flex: 1 }}>
{component}
</View>
)
So yeah watch out where your SectionList is defined and how many parents it has, I think it required only one
Hope this helps.
I was able to fix mine by adding this prop to the section list
stickySectionHeadersEnabled={false}
I added a marginBottom to the SectionList of equal amount of space consumed by the View on top to equalise the area.

Bring View on top of Modal using zIndex style with React-Native

zIndex has been introduced recently to React-Native to change the position of a View in the stack of layers.
Although, I'm not able to bring a View on top of a Modal component.
My code looks like this:
render() {
return (
<View>
<Modal visible>
{props.children}
</Modal>
<View style={{ zIndex: 1000 }}>
<Text>Loading...</Text>
</View>
</View>
);
}
I guess I could stop using <Modal> and create a regular animated <View> that would behave like the Modal, but I'd rather find another solution.
Any idea?
No amount of zIndex manipulation will bring something above a react-native Modal, unfortunately. The Modal component is a native view that sits on top of the rest of your react-native application. The only way to put something above it is to put something in the modal itself, or alternately to use a js only implementation of a Modal.
Incidentally, the react-native-community version of modal is also built on the react-native modal, so would have the same issue. There's a discussion about different js implementation here:
https://github.com/react-native-community/react-native-modal/issues/145
Not possible with modal. As the modal should always shown regardless of whatever the zIndex is given to it and other components in the screen
It will always shown you unless you make visible=false
To implement what you want. You could use a absolutely positioned view with some zIndex trick to move this view back and front.
render() {
return (
<View>
<View style={{position:'absolute',top:0,bottom:0,left:0,right:0,zIndex:visible?-1:2}}>
{props.children}
</View>
<View style={{ zIndex: 1 }}>
<Text>Loading...</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
modal: {
backgroundColor: 'white',
margin: 0, // This is the important style you need to set
alignItems: undefined,
justifyContent: undefined,
}
<Modal isVisible={true} style={styles.modal}>
<View style={{ flex: 1 }}>
<Text>Hello!</Text>
</View>
</Modal>
I was struggling with the same problem and finally, I solved it. here's what I found:
First, the problem:
the problem is the modal appears on top of all screen layers which means it will show on all app elements, so if you open two modals at the same time the app will make it all on each other cuz the app don't know what the modals order it should be, for that reason, you see your modal behind the other one.
Second, the solution:
to make the second modal opens on the first modal you need to arrange the order of the modals opening, which means you open the parent modal then open the child modal.
and for doing that I'm using promise which allow me to do that like this steps example:-
1 - create promise
const [isOpen, setOpen] = useState({ backDrop: false, modal: false }); // modals open state
function openDialog() {
return new Promise((resolve) => {
setOpen((previous) => ({ ...previous, backDrop: props.isOpen }))
resolve()
})
}
2 - I use useEffect() to trigger modal opening props and then invoke an async function which will await the openDialog() function at the first step above and then will set open state after the first function invoked.
useEffect(() => {
(async function () {
await openDialog()
setOpen((previous) => ({ ...previous, modal: props.isOpen }))
}())
}, [props.isOpen])
this how you can control opening the modals pre-arranged and on top of each other
You have to change the z-index of the modal not the one of the view (and a z-index of value 1 would suffice):
render() {
return (
<View>
<Modal visible style={{ zIndex: 1 }}>
{props.children}
</Modal>
<View>
<Text>Loading...</Text>
</View>
</View>
);
}
An element with a larger z-index generally covers an element with a lower one (MDN docs).
EDIT:
Another solution is to change the order of the elements:
render() {
return (
<View>
<View>
<Text>Loading...</Text>
</View>
<Modal visible>
{props.children}
</Modal>
</View>
);
}
With this solution you don't need z-index because the modal is already on top of the view.

Categories