Skip to content

fix: handle broken measureInWindow#1355

Merged
kirillzyusko merged 14 commits intokirillzyusko:mainfrom
thomasttvo:fix/kav-fabric-modal
Mar 12, 2026
Merged

fix: handle broken measureInWindow#1355
kirillzyusko merged 14 commits intokirillzyusko:mainfrom
thomasttvo:fix/kav-fabric-modal

Conversation

@thomasttvo
Copy link
Copy Markdown
Contributor

@thomasttvo thomasttvo commented Mar 11, 2026

📜 Description

Fixed broken measureInWindow measurements in react-native.

💡 Motivation and Context

The measureInWindow works unreliably in Fabric + formSheet Modal (facebook/react-native#56062) or on Android with edge-to-edge enabled (facebook/react-native#56056)

Upstream fixes are available, but the problem is that it will require at least RN 0.85+ to work properly. So in this PR we add internal function that is capable of measuring given view. In future this functionality can be removed, but for now this is critical to have it bundled within a package.

Fixes #1356

📢 Changelog

JS

  • added viewPositionInWindow to types;
  • added viewPositionInWindow to codegen;
  • added viewPositionInWindow to bindings/module;
  • use viewPositionInWindow instead of measureInWindow;
  • added viewPositionInWindow to mocks;

iOS

  • implement viewPositionInWindow;
  • make activeWindow objc available;

Android

  • implement viewPositionInWindow;
  • add uiManager and eventDispatcher to ReactContext extensions;

🤔 How Has This Been Tested?

Tested manually on iPhone 17 Pro (iOS 26.2, simulator) and Pixel 9 Pro (API 35, emulator).

📸 Screenshots (if appropriate):

Android

Fabric

Before After
Screen.Recording.2026-03-12.at.16.34.28.mov
Screen.Recording.2026-03-12.at.16.27.10.mov

Paper

Before After
Screen.Recording.2026-03-12.at.16.30.43.mov
Screen.Recording.2026-03-12.at.16.21.48.mov

iOS

Fabric

Before After
Simulator.Screen.Recording.-.iPhone.17.Pro.-.2026-03-12.at.16.38.23.mov
Simulator.Screen.Recording.-.iPhone.17.Pro.-.2026-03-12.at.16.25.29.mov

Paper

Before After
Simulator.Screen.Recording.-.iPhone.17.Pro.-.2026-03-12.at.16.29.28.mov
Simulator.Screen.Recording.-.iPhone.17.Pro.-.2026-03-12.at.16.18.25.mov

📝 Checklist

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

On Fabric, measureInWindow returns modal-surface-relative coordinates
instead of screen-absolute coordinates for views inside a Modal (RN
bug #52450). This causes automaticOffset to compute incorrect shift
values, leaving content hidden behind the keyboard.

Detect the bug by comparing measureInWindow results with onLayout
coordinates. When they match, estimate absolute Y by assuming the
view extends to the bottom of the screen. Also preserve the
pre-keyboard frame dimensions when automaticOffset is enabled to
prevent iOS modal keyboard adjustment from shrinking the frame
mid-animation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 11, 2026

📊 Package size report

Current size Target Size Difference
303074 bytes 301499 bytes 1575 bytes 📈

@thomasttvo
Copy link
Copy Markdown
Contributor Author

Sorry I know you've been working on this. Let me review this PR to see what's up. It's my agent creating this automatically @kirillzyusko.

thomasvo and others added 8 commits March 11, 2026 13:29
…InWindow

Replace the heuristic detection of broken measureInWindow with a native
windowPosition method that uses UIKit/Android view APIs to get true
screen-absolute coordinates. This correctly handles Fabric + Modal where
measureInWindow returns surface-relative coordinates (RN bug #52450).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Pass both x and y from windowPosition (matching original measureInWindow behavior)
- Add .catch() to handle view-not-found rejection gracefully

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
On Paper architecture, viewWithTag may not find the view, causing
windowPosition to reject. Fall back to measureInWindow which returns
correct absolute coordinates on Paper.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fall back to onLayout values when findNodeHandle returns null
  instead of silently dropping the layout event
- Replace deprecated keyWindow with UIWindowScene API for iOS 13+

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Import findNodeHandle from library's utils wrapper (consistent with
  KeyboardAwareScrollView usage) instead of directly from react-native
- Add view.superview nil check in iOS windowPosition to avoid silently
  resolving with CGRectZero when superview is nil

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@thomasttvo
Copy link
Copy Markdown
Contributor Author

@kirillzyusko I took a look at this and it seems like an interesting direction. I know you're working on your own solution and submitted a PR for the RN team, but do you think this approach here would be something to consider?

Copy link
Copy Markdown
Owner

@kirillzyusko kirillzyusko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall I like the idea. I left few comments what can be improved in current implementation - in a meantime I'll test Android/iOS both architectures to check whether it works or not!

Comment thread ios/KeyboardControllerModule.mm Outdated
Comment thread src/bindings.ts Outdated
Comment thread src/types/module.ts Outdated
@kirillzyusko
Copy link
Copy Markdown
Owner

kirillzyusko commented Mar 12, 2026

Paper/Android:

image

This is because:

Error: Could not find view for tag
    at promiseMethodWrapper (10.0.2.2:8081/index.bundle//&platform=android&dev=true&lazy=true&minify=false&app=com.example.reactnativekeyboardcontroller&modulesOnly=false&runModule=true&excludeSource=true&sourcePaths=url-server:2445:45)
    at anonymous (10.0.2.2:8081/index.bundle//&platform=android&dev=true&lazy=true&minify=false&app=com.example.reactnativekeyboardcontroller&modulesOnly=false&runModule=true&excludeSource=true&sourcePaths=url-server:137449:100)
    at executeDispatch (10.0.2.2:8081/index.bundle//&platform=android&dev=true&lazy=true&minify=false&app=com.example.reactnativekeyboardcontroller&modulesOnly=false&runModule=true&excludeSource=true&sourcePaths=url-server:77718:17)
    at runWithFiberInDEV (10.0.2.2:8081/index.bundle//&platform=android&dev=true&lazy=true&minify=false&app=com.example.reactnativekeyboardcontroller&modulesOnly=false&runModule=true&excludeSource=true&sourcePaths=url-server:77696:135)
    at executeDispatchesAndReleaseTopLevel (10.0.2.2:8081/index.bundle//&platform=android&dev=true&lazy=true&minify=false&app=com.example.reactnativekeyboardcontroller&modulesOnly=false&runModule=true&excludeSource=true&sourcePaths=url-server:77992:84)
    at call (native)
    at forEachAccumulated (10.0.2.2:8081/index.bundle//&platform=android&dev=true&lazy=true&minify=false&app=com.example.reactnativekeyboardcontroller&modulesOnly=false&runModule=true&excludeSource=true&sourcePaths=url-server:77861:67)
    at anonymous (10.0.2.2:8081/index.bundle//&platform=android&dev=true&lazy=true&minify=false&app=com.example.reactnativekeyboardcontroller&modulesOnly=false&runModule=true&excludeSource=true&sourcePaths=url-server:78014:29)
    at batchedUpdatesImpl (10.0.2.2:8081/index.bundle//&platform=android&dev=true&lazy=true&minify=false&app=com.example.reactnativekeyboardcontroller&modulesOnly=false&runModule=true&excludeSource=true&sourcePaths=url-server:86481:18)
    at batchedUpdates$1 (10.0.2.2:8081/index.bundle//&platform=android&dev=true&lazy=true&minify=false&app=com.example.reactnativekeyboardcontroller&modulesOnly=false&runModule=true&excludeSource=true&sourcePaths=url-server:77978:34)
    at _receiveRootNodeIDEvent (10.0.2.2:8081/index.bundle//&platform=android&dev=true&lazy=true&minify=false&app=com.example.reactnativekeyboardcontroller&modulesOnly=false&runModule=true&excludeSource=true&sourcePaths=url-server:78003:23)
    at receiveEvent (10.0.2.2:8081/index.bundle//&platform=android&dev=true&lazy=true&minify=false&app=com.example.reactnativekeyboardcontroller&modulesOnly=false&runModule=true&excludeSource=true&sourcePaths=url-server:84702:32)
    at apply (native)
    at __callFunction (10.0.2.2:8081/index.bundle//&platform=android&dev=true&lazy=true&minify=false&app=com.example.reactnativekeyboardcontroller&modulesOnly=false&runModule=true&excludeSource=true&sourcePaths=url-server:2877:38)
    at anonymous (10.0.2.2:8081/index.bundle//&platform=android&dev=true&lazy=true&minify=false&app=com.example.reactnativekeyboardcontroller&modulesOnly=false&runModule=true&excludeSource=true&sourcePaths=url-server:2633:31)
    at __guard (10.0.2.2:8081/index.bundle//&platform=android&dev=true&lazy=true&minify=false&app=com.example.reactnativekeyboardcontroller&modulesOnly=false&runModule=true&excludeSource=true&sourcePaths=url-server:2823:15)
    at callFunctionReturnFlushedQueue (10.0.2.2:8081/index.bundle//&platform=android&dev=true&lazy=true&minify=false&app=com.example.reactnativekeyboardcontroller&modulesOnly=false&runModule=true&excludeSource=true&sourcePaths=url-server:2632:21)

I think you wrote:

val view = mReactContext.currentActivity?.findViewById<View>(viewTag.toInt())

Which may be not correct, because Modal will live in separate Context/Activity

P. S. fixed in 404e936

@kirillzyusko kirillzyusko added the KeyboardAvoidingView 🧪 Anything related to KeyboardAvoidingView component label Mar 12, 2026
@kirillzyusko kirillzyusko marked this pull request as ready for review March 12, 2026 15:24
@kirillzyusko kirillzyusko changed the title fix: handle broken measureInWindow on Fabric + Modal fix: handle broken measureInWindow Mar 12, 2026
@kirillzyusko kirillzyusko merged commit eb2dfbb into kirillzyusko:main Mar 12, 2026
25 checks passed
@kirillzyusko
Copy link
Copy Markdown
Owner

I changed code a little bit, but thank you again for the contribution! ❤️

@thomasttvo
Copy link
Copy Markdown
Contributor Author

I changed code a little bit, but thank you again for the contribution! ❤️

Thanks for taking it to the finish line! @kirillzyusko

@kirillzyusko
Copy link
Copy Markdown
Owner

My pleasure 😊

@kirillzyusko kirillzyusko added the 🌎 modal Anything that involves Modal usage label Mar 12, 2026
kirillzyusko added a commit that referenced this pull request Mar 12, 2026
)

## 📜 Description

Automatically detect top border of `KeyboardAwareScrollView`.

## 💡 Motivation and Context

Continue the epic with better discovery of component location on the
screen and logical continuation of
#1346

In this PR I started to detect relative position of `ScrollView`, so
that I better understand if caret is not visible because it obscured by
other elements (header etc.) It's still not perfectly implemented and
there is still a "blind"/"dead" zone where text is already hidden but
scroll doesn't happen. I'll fix it in following PRs but for now I just
want to bring these changes to upcoming `1.21.0` release 🤞

We also can't use `measureInWindow` because it produces incorrect
measurements, so we need to use our custom implementation that has been
added in
#1355

Significantly improves UI for behavior described in
#1341

## 📢 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 -->

### JS

- auto detect position of `KeyboardAwareScrollView` on the screen to
better understand top border of the component relative to screen;

## 🤔 How Has This Been Tested?

Tested manually on iPhone 17 Pro (iOS 26.2, simulator).

## 📸 Screenshots (if appropriate):


https://github.com/user-attachments/assets/43fe233f-9c83-40c9-9642-a68e2d8e8e7c

## 📝 Checklist

- [x] CI successfully passed
- [x] I added new mocks and corresponding unit-tests if library API was
changed
@kirillzyusko kirillzyusko mentioned this pull request Apr 27, 2026
2 tasks
kirillzyusko added a commit that referenced this pull request Apr 27, 2026
## 📜 Description

Fixed `IllegalViewOperationException` crash on Android.

## 💡 Motivation and Context

Original code has been introduced in
#1355
and further started to be used in
#1352

Now if view is not found it throws an unhandled exception and crashes
the app. In reality we already handle "view not found" exception, so we
should just add a proper try/catch block to handle this error correctly
in Kotlin code 🤞

Closes
#1443

## 📢 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 -->

### Android

- wrap `resolveView` with `catch (e: IllegalViewOperationException)`;

## 🤔 How Has This Been Tested?

Tested via e2e pipeline.

## 📝 Checklist

- [x] CI successfully passed
- [x] I added new mocks and corresponding unit-tests if library API was
changed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🤖 android Android specific enhancement New feature or request 🍎 iOS iOS specific KeyboardAvoidingView 🧪 Anything related to KeyboardAvoidingView component 🌎 modal Anything that involves Modal usage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

keyboardVerticalOffset is unreliable as useHeaderHeight produces different measurements for different phones

2 participants