Skip to content

[iOS] ScrollView automaticallyAdjustKeyboardInsets ignores changes made while the keyboard is visible #59305

Open

Description

@mozzius

Description

On iOS, ScrollView's automaticallyAdjustKeyboardInsets only takes effect when a UIKeyboardWillChangeFrameNotification arrives. If the prop changes while the keyboard is already on screen, nothing happens:

  • Turned on while the keyboard is up: no keyboard inset is applied. The end of the content stays behind the keyboard and can't be scrolled into view until the keyboard is hidden and shown again.
  • Turned off while the keyboard is up: the keyboard inset stays. When the keyboard is then dismissed, its hide notification is ignored too, since the prop is now off, so the inset is never removed. That leaves a keyboard-sized gap at the end of the content for good.

In RCTScrollViewComponentView.mm, _keyboardWillChangeFrame: returns early when _automaticallyAdjustKeyboardInsets is NO, and updateProps only stores the new value of the flag. Nothing applies or removes the inset when the value changes.

How we hit it: in the Bluesky app, bottom sheets turn automaticallyAdjustKeyboardInsets on only when the sheet reports it's at its full-height detent, because at smaller detents UIKit moves the sheet above the keyboard itself. That report is an event from native, so it reaches the ScrollView's props shortly after the sheet starts presenting. A sheet with an autofocused TextInput raises the keyboard during presentation, before the event lands, so its ScrollView never gets the inset and the user can't scroll to the button at the bottom. Dragging a sheet to full height while typing hits the same gap. More generally, any ScrollView whose automaticallyAdjustKeyboardInsets depends on state that can change while the keyboard is up is affected.

Steps to reproduce

  1. Clone https://github.com/mozzius/scrollview-keyboard-insets-repro, then cd ReproducerApp && yarn && (cd ios && bundle install && bundle exec pod install) && yarn ios --mode Release.
  2. A: with the automaticallyAdjustKeyboardInsets Switch off, tap the text input. Turn the Switch on (the keyboard stays up), then scroll to the bottom.
  3. B: with the Switch on, tap the text input. Turn the Switch off, press Return to dismiss the keyboard, then scroll to the bottom.

The readout under the Switch shows the keyboard height and the contentInset.bottom from the last onScroll event.

Expected: A gets the keyboard inset (335pt here) and the yellow "End of content" block scrolls into view above the keyboard. In B, the inset goes back to 0 once the keyboard is gone.

Actual:

contentInset.bottom
Control: prop on before the keyboard appears 335 works
A: prop turned on with the keyboard up 0 end of the content stuck behind the keyboard
B: prop turned off with the keyboard up, then keyboard hidden 335 permanent blank gap under the content

Possible fix

Keep the keyboard's end frame from every notification, including those that arrive while the prop is off. When updateProps sees the prop change while the keyboard is visible, apply the inset as if the keyboard had just appeared, or remove it and clamp the offset. The repro's fixed branch applies that as a patch (RN built from source): A gets 335, B goes back to 0, and flipping the prop while the keyboard is hidden changes nothing. PR: #59306

Related but different: #56189 (stale gap after dismissal with contentInsetAdjustmentBehavior="automatic") and #57755 (the flag surviving view recycling).

React Native Version

0.88.0

Affected Platforms

Runtime - iOS, Build - MacOS

Output of npx @react-native-community/cli info

System:
  OS: macOS 27.0.1
  CPU: (14) arm64 Apple M4 Pro
  Memory: 2.34 GB / 48.00 GB
  Shell:
    version: 5.3.20
    path: /opt/homebrew/bin/bash
Binaries:
  Node:
    version: 24.19.0
    path: /Users/samuel/.nvm/versions/node/v24.19.0/bin/node
  Yarn:
    version: 1.22.22
    path: /Users/samuel/.nvm/versions/node/v24.19.0/bin/yarn
  npm:
    version: 11.17.0
    path: /Users/samuel/.nvm/versions/node/v24.19.0/bin/npm
  Watchman:
    version: 2026.10.19.00
    path: /opt/homebrew/bin/watchman
Managers:
  CocoaPods:
    version: 1.17.0
    path: /Users/samuel/.rbenv/shims/pod
SDKs:
  iOS SDK:
    Platforms:
      - DriverKit 27.0
      - iOS 27.1
      - macOS 27.0
      - tvOS 27.0
      - visionOS 27.0
      - watchOS 27.0
  Android SDK:
    API Levels:
      - "29"
      - "33"
      - "34"
      - "35"
      - "36"
      - "37"
    Build Tools:
      - 30.0.3
      - 34.0.0
      - 35.0.0
      - 35.0.1
      - 36.0.0
      - 37.0.0
    System Images:
      - android-28 | Google ARM64-V8a Play ARM 64 v8a
      - android-29 | Google Play ARM 64 v8a
      - android-30 | Google APIs ARM 64 v8a
      - android-34 | Google Play ARM 64 v8a
      - android-35 | Google Play ARM 64 v8a
      - android-35 | Google Play Tablet ARM 64 v8a
      - android-36 | Google Play ARM 64 v8a
    Android NDK: Not Found
IDEs:
  Android Studio: 2026.1 AI-261.26222.65.2614.16379836
  Xcode:
    version: 27.1/27A9275
    path: /usr/bin/xcodebuild
Languages:
  Java:
    version: 17.0.20.1
    path: /usr/bin/javac
  Ruby:
    version: 2.7.6
    path: /Users/samuel/.rbenv/shims/ruby
npmPackages:
  "@react-native-community/cli":
    installed: 20.2.0
    wanted: 20.2.0
  react:
    installed: 19.3.0
    wanted: 19.3.0
  react-native:
    installed: 0.88.0
    wanted: 0.88.0
  react-native-macos: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: true
  newArchEnabled: true
iOS:
  hermesEnabled: true
  newArchEnabled: true

Stacktrace or Logs

No crash or error. The ScrollView's contentInset.bottom (from onScroll) stays at 0 after the prop is turned on with the keyboard up, and stays at the keyboard height (335) after the prop is turned off and the keyboard is dismissed.

MANDATORY Reproducer

https://github.com/mozzius/scrollview-keyboard-insets-repro

Screenshots and Videos

iPhone 17 simulator, iOS 26.5, Release.

A, stock A, fixed B, stock B, fixed
A, stock: prop on, keyboard 335, contentInset.bottom 0, end of the content behind the keyboard A, fixed: contentInset.bottom 335, end of the content above the keyboard B, stock: keyboard gone but contentInset.bottom still 335, blank gap under the content B, fixed: contentInset.bottom 0, no gap

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions