Skip to content

fix: wrong offset for KeyboardAwareScrollView when keyboard resizes#1407

Closed
kirillzyusko wants to merge 7 commits intomainfrom
fix/kasv-keyboard-resize
Closed

fix: wrong offset for KeyboardAwareScrollView when keyboard resizes#1407
kirillzyusko wants to merge 7 commits intomainfrom
fix/kasv-keyboard-resize

Conversation

@kirillzyusko
Copy link
Copy Markdown
Owner

@kirillzyusko kirillzyusko commented Mar 27, 2026

📜 Description

Fixed an issue when resizing keyboard constantly pushes content up.

💡 Motivation and Context

When a user switches between keyboard types (e.g. text keyboard → emoji keyboard and back), KeyboardAwareScrollView was accumulating scroll offset instead of converging to a stable position. Each toggle would push the scroll further, even though the input's actual on-screen position hadn't changed. It's caused by 2 bugs:

1️⃣ Stale absoluteY during keyboard resize

maybeScroll uses layout.value.absoluteY to determine how much to scroll. This value is populated by the native FocusedInputLayoutChanged event. On iOS, the native observer uses KVO on the input's center relative to its superview (the scroll view's content area). When the scroll view scrolls, the input's center relative to the content doesn't change — only the scroll view's bounds.origin shifts — so KVO never fires and absoluteY is never updated.

As a result, when the emoji keyboard opens and the component scrolls (say) +76px, the stored absoluteY still reflects the pre-scroll position. When switching back to the text keyboard, maybeScroll computes an overshoot based on that stale value, producing a cumulative drift on every toggle.

On Android the native side does report updated absoluteY on scroll, so only iOS is affected — but the stale-value case is latent everywhere.

2️⃣ scrollBeforeKeyboardMovement was clobbered on every keyboard resize

This value is used as the interpolation base when the keyboard hides: we scroll back to it. It was being overwritten on every onStart event, including keyboard-size-only changes (emoji ↔ text). That meant after one toggle, the "home position" shifted, and hiding the keyboard would scroll to the wrong place.


The fix consist from two parts.

For 1️⃣ problem - when onStart fires for a keyboard-size change on the same input (keyboardWillChangeSize && !actualFocusChanged), we recompute absoluteY from first principles:

absoluteY = layout.y - position.value + scrollViewPageY

layout.y is the input's Y offset relative to the scroll view's content — this is stable and never stale. Subtracting the current scroll offset and adding the scroll view's on-screen origin gives the true current position on screen, regardless of what the native side reported.

For 2️⃣ problem - we gate the scrollBeforeKeyboardMovement update behind actualFocusChanged. Keyboard-size-only changes (same target, different height) no longer disturb this value, so the scroll-back-on-hide always returns to the position captured at the original focus event.

Closes #1318

📢 Changelog

JS

  • added actualFocusChanged variable inside onStart handler;
  • update scrollBeforeKeyboardMovement only if actualFocusChanged===true (not focusWasChanged);
  • covered new code with unit tests;
  • update layout.value.absoluteY when keyboard gets resized;

🤔 How Has This Been Tested?

Tested paper arch on:

  • Pixel 9 Pro (API 35)
  • iPhone 17 Pro (iOS 26.2)

📸 Screenshots (if appropriate):

iOS Android
Simulator.Screen.Recording.-.iPhone.17.Pro.-.2026-03-31.at.15.44.27.mov
Screen.Recording.2026-03-31.at.15.46.18.mov

📝 Checklist

  • CI successfully passed
  • I added new mocks and corresponding unit-tests if library API was changed

@kirillzyusko kirillzyusko self-assigned this Mar 27, 2026
@kirillzyusko kirillzyusko added 🐛 bug Something isn't working KeyboardAwareScrollView 📜 Anything related to KeyboardAwareScrollView component labels Mar 27, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 27, 2026

📊 Package size report

Current size Target Size Difference
310446 bytes 309372 bytes 1074 bytes 📈

@kirillzyusko kirillzyusko changed the title Fix/kasv keyboard resize fix: wrong offset for KeyboardAwareScrollView when keyboard resizes Mar 27, 2026
@kirillzyusko kirillzyusko added the tests You added or changed unit tests label Mar 31, 2026
@kirillzyusko kirillzyusko marked this pull request as ready for review March 31, 2026 12:50
kirillzyusko added a commit that referenced this pull request Mar 31, 2026
… vs text) (#1417)

## 📜 Description

Fixed a problem when switch text -> emoji -> text keeps pushing the
content inside `KeyboardAwareScrollView` further and further.

## 💡 Motivation and Context

The issue has been introduced in this PR:
#760

The problem with:

```swift
if UIResponder.current == currentResponder {
  return
}
```

Is that when keyboard resizes we never emit `syncUpLayout` event. The
fix proposed in this PR is safe, because:
- we don't run full input listeners mount cycle;
- we only emit additional event;
- it's safe to emit this event multiple times (i. e. two times on focus)
because we have this guard:

```swift
    if NSDictionary(dictionary: data).isEqual(to: lastEventDispatched) {
      return
    }
```

Another attempt to fix it has been added here
#1407
But in
#1407
we are trying to make JS code even more complex and the main problem is
that with this fix documentation stays incorrect:

> when keyboard changes its size (appears, disappears, changes size
because of different input mode);

So this is a better fix that fixes a native side of the platform.

Closes
#1318
#1407

## 📢 Changelog

<!-- High level overview of important changes -->
<!-- For example: fixed status bar manipulation; added new types
declarations; -->
<!-- If your changes don't affect one of platform/language below - then
remove this platform/language -->

### iOS

- dispatch `syncUpLayout` if this event comes from keyboard
notification;

## 🤔 How Has This Been Tested?

Tested manually on iPhone 17 Pro (iOS 26.2) paper arch.

## 📸 Screenshots (if appropriate):

|Before|After|
|-------|-----|
|<video
src="https://github.com/user-attachments/assets/dab83232-e9c8-47f3-a128-5ebac517a9a4">|<video
src="https://github.com/user-attachments/assets/50f74da1-a75f-458d-9f6f-e382fd688b0b">|

## 📝 Checklist

- [x] CI successfully passed
- [x] I added new mocks and corresponding unit-tests if library API was
changed
@kirillzyusko
Copy link
Copy Markdown
Owner Author

Closing in favour of #1417

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

Labels

🐛 bug Something isn't working KeyboardAwareScrollView 📜 Anything related to KeyboardAwareScrollView component tests You added or changed unit tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Prevent vertically centered TextInput from jumping when keyboard resizes

1 participant