Set contentInset and contentOffset in React Native's ScrollView

Viewed 24713

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?

5 Answers

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

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:

  1. 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:

  1. 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