Skip to content

Pull to refresh

A platform-split recipe — iOS and Android overscroll differently.

Because content is padded below the header, the native RefreshControl spinner renders at the content origin — tucked behind the header. The robust recipe differs by platform, because the two platforms overscroll differently.

Android — lists don’t bounce

The offset stays clamped at 0 (you get a stretch/glow), so an offset-driven custom pull can’t work. Use the native RefreshControl and let the adapter’s default progressViewOffset (= header height) push its spinner below the header, or set it yourself.

iOS — lists bounce

contentOffset.y goes negative past the top. Read that pull distance from useCurrentTabScrollY() and drive your own spinner pinned in the visible area (below the header) — the native iOS spinner would be hidden behind the header.

const scrollY = useCurrentTabScrollY();   // negative on iOS = pull distance
const { height } = useHeaderMeasurements();

<Tabs.FlatList
  refreshControl={
    Platform.OS === 'android'
      ? <RefreshControl refreshing={busy} onRefresh={refresh}
          progressViewOffset={height} />
      : undefined   // iOS: render a custom spinner driven by scrollY instead
  }
/>