Skip to content

[iOS] Bottom items not rendered when scrolling to end with variable-height items (Android works fine) #1951

@saif-techversant

Description

@saif-techversant

Description

When using FlashList v2 with dynamic-sized list items (form fields with varying heights), the bottom section of the list is not rendered on iOS when scrolling to the end.

The same code works perfectly on Android, where all items are rendered correctly.

This issue is not related to the keyboard or inputs expanding — it occurs even with static items that have variable heights.

Current behavior

On iOS:

  • The last few items are not displayed.
  • The list footer and padding are visible, meaning the list's end is calculated, but the items just above the footer are not rendered
  • Increasing drawDistance values fixes the rendering issue but causes performance problems and inconsistent behavior on other forms with different item counts

On Android:

  • All items render as expected with no issues

Steps to reproduce:

  1. Create a FlashList with 40–50+ form fields of varying heights (text inputs, dropdowns, date pickers, signature fields, etc.)
  2. Scroll to the bottom of the list on iOS
  3. Observe that some bottom items are missing or not rendered until you slightly scroll back up
  4. Run the same code on Android — the full list is visible and renders correctly

Expected behavior

All list items should render correctly on iOS, just as they do on Android, when scrolling to the bottom. The list should not require platform-specific drawDistance calculations to render all items properly.

Reproduction

⚠️ Note: This issue occurs in a complex production app with form fields and dynamic content. A minimal reproduction would require setting up:

  • Multiple form field types with varying heights
  • 40-50+ items in the list
  • ScrollToIndex functionality
  • Dynamic content updates

Code from production app:

<FlashList
  ref={flatListRef}
  data={fields}
  extraData={[fields, formData?.defined, formHidden]}
  renderItem={renderItem}
  keyExtractor={(item, index) => {
    const baseKey = item.id || item.dbId || `item_${index}`;
    const typeKey = item.type || 'unknown';
    return `${baseKey}_${typeKey}_${index}`;
  }}
  getItemType={item => item.type || 'default'}
  // === ISSUE: drawDistance needs platform-specific adjustment ===
  // This workaround doesn't fully work - causes issues on some forms
  drawDistance={
    Platform.OS === 'ios'
      ? Math.round(
          screenContext.windowHeight *
            Math.max(1.321, visibleFields?.length / 42),
        )
      : Math.round(screenContext.windowHeight) // Works fine on Android
  }
  maxItemsInRecyclePool={100}
  maintainVisibleContentPosition={{
    minIndexForVisible: 0,
    autoscrollToTopThreshold: 1,
  }}
  overrideProps={{
    getItemCount: () => visibleFields?.length || 0,
    getItem: (data, index) => data?.[index],
  }}
  removeClippedSubviews={false}
  scrollEventThrottle={32}
  decelerationRate="fast"
  nestedScrollEnabled={true}
  keyboardDismissMode="interactive"
  keyboardShouldPersistTaps="always"
  contentContainerStyle={{
    paddingBottom: screenContext.windowHeight * 0.05,
  }}
  viewabilityConfig={{
    minimumViewTime: 100,
    itemVisiblePercentThreshold: 10,
  }}
/>

Expo Snack or minimal reproduction link:

Currently not available - this occurs in a production React Native app with complex form logic. Will work on creating a minimal reproduction if needed.

Platform

  • iOS
  • Android (works correctly)
  • Web

Environment

React Native info output:
System:
  OS: macOS 26.0.1
  CPU: (10) arm64 Apple M4
  Memory: 143.42 MB / 16.00 GB
  Shell:
    version: "5.9"
    path: /bin/zsh
Binaries:
  Node:
    version: 23.11.0
    path: /opt/homebrew/bin/node
  Yarn:
    version: 1.22.22
    path: /opt/homebrew/bin/yarn
  npm:
    version: 10.9.2
    path: /opt/homebrew/bin/npm
  Watchman:
    version: 2025.04.14.00
    path: /opt/homebrew/bin/watchman
Managers:
  CocoaPods:
    version: 1.16.2
    path: /Users/saif/.rbenv/shims/pod
SDKs:
  iOS SDK:
    Platforms:
      - DriverKit 25.0
      - iOS 26.0
      - macOS 26.0
      - tvOS 26.0
      - visionOS 26.0
      - watchOS 26.0
  Android SDK:
    API Levels:
      - "35"
    Build Tools:
      - 35.0.0
    Android NDK: Not Found
IDEs:
  Android Studio: 2025.1 AI-251.26094.121.2513.14007798
  Xcode:
    version: 26.0.1/17A400
    path: /usr/bin/xcodebuild
Languages:
  Java:
    version: 17.0.15
    path: /usr/bin/javac
  Ruby:
    version: 3.2.2
    path: /Users/saif/.rbenv/shims/ruby
npmPackages:
  "@react-native-community/cli":
    installed: 20.0.0
    wanted: 20.0.0
  react:
    installed: 19.1.0
    wanted: 19.1.0
  react-native:
    installed: 0.81.4
    wanted: 0.81.4
  react-native-macos: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: true
  newArchEnabled: true
iOS:
  hermesEnabled: true
  newArchEnabled: true

FlashList version: 2.1.0

Additional context

Attempted Workaround (Not Fully Working)

We've tried implementing a platform-specific dynamic drawDistance calculation for iOS:

drawDistance={
  Platform.OS === 'ios'
    ? Math.round(windowHeight * Math.max(1.321, visibleFields.length / 42))
    : Math.round(windowHeight)
}

Why this workaround fails:

  • ❌ It requires magic numbers (1.321, 42) that don't scale properly
  • ❌ Different forms with different item counts need different multipliers
  • ❌ When the multiplier is too high, it causes rendering issues and performance problems on some forms
  • ❌ When the multiplier is too low, bottom items don't render on other forms
  • ❌ It's unreliable and inconsistent across different forms in the same app
  • ✅ Android works perfectly with just Math.round(windowHeight) without any adjustments

The core issue: There's no reliable formula to calculate the correct drawDistance for iOS across different forms with varying item counts and heights. The value that fixes one form breaks another.

Analysis

  • This happens only on iOS (tested on iOS 17.x, both simulator and real devices)
  • Keyboard, SafeAreaView, and TextInput resizing are not related to the issue
  • Appears to be a viewport measurement or virtualization issue specific to iOS layout calculations
  • Setting a higher drawDistance (e.g., windowHeight * 1.5 or using a formula based on item count) fixes the rendering but introduces other problems

Key Questions

  1. Why does iOS require a larger drawDistance than Android for the same content?
  2. Is there a way to make FlashList automatically calculate the correct drawDistance based on the actual content height?
  3. Can the viewport prediction algorithm be improved to handle variable-height items more accurately on iOS?

Root cause hypothesis: FlashList v2's viewport prediction on iOS might slightly under-measure the scrollable area when dealing with variable-height items, causing the last few items to fall outside the visible rendering window until re-scrolled. The Android implementation appears to handle this correctly.

Checklist

  • I've searched existing issues and couldn't find a duplicate
  • I've provided a minimal reproduction (working on it)
  • I'm using the latest version of @shopify/flash-list
  • I've included all required information above

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1Important but not urgentbugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions