You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix: report the iOS caret y of single-line inputs from the field's top - #1691
Report the caret y of a single-line TextInput on iOS from the field's top, as Android does, so KeyboardAwareScrollView scrolls a padded single-line input fully above the keyboard.
💡 Motivation and Context
textSelection(in:) sends caretRect(for:) as it is. That rect is in textInputView's coordinates, and for a UITextField that's the inner text area, which React Native places inside the field's paddingTop. So the caret of a single-line input came out one top padding higher than it is.
KeyboardAwareScrollView adds selection.end.y to the input's absoluteY (updateLayoutFromSelection) and scrolls until that point clears the keyboard plus bottomOffset. With the caret reported too high, it stopped one top padding short: with paddingTop: 30 and bottomOffset: 20, the typed text ended up under the keyboard.
Android already measures from the field's top: KeyboardControllerSelectionWatcher adds paddingTop (and the gravity offset) to the line's top and bottom.
Only UITextField changes. caretYOffset returns 0 for a UITextView, so multiline inputs keep reporting what they do today.
x is unchanged, matching Android, where x comes from getPrimaryHorizontal without the horizontal padding.
📢 Changelog
iOS
report the caret y of single-line inputs (UITextField) from the field's top, adding the offset of textInputView inside the field
🤔 How Has This Been Tested?
example app (Paper, RN 0.81.4), iPhone 16 simulator, iOS 18.6, on-screen keyboard. The "Aware scroll view" screen temporarily replaced with single-line inputs (height: 70, paddingTop: 30, bottomOffset={20}; snippet below), with an on-screen readout of selection.end.y from useFocusedInputHandler. Focused #5 and typed "hello":
before: selection.end.y = 27.3 and the text sits under the keyboard
after: selection.end.y = 57.3 (+30, the top padding) and the text stays above the keyboard
A production app on the New Architecture (RN 0.86, 1.22.7 with this change as a patch): a single-line input with paddingTop: 18 went from 35.3 to 53.3 and now clears the keyboard. In a @gorhom/bottom-sheet with KeyboardAwareScrollView, a single-line input now scrolls by its top padding more (68pt instead of 50pt) and stays visible; multiline inputs don't change.
Not added: a native unit test. KCTextInputCompositeDelegate.swift isn't compiled into KeyboardControllerNativeTests; happy to add it there with a test if you'd like.
Incorrect y-coordinate calculation in textSelection function for end position Why: The end position's y-coordinate is being adjusted by adding caretRectEnd.size.height and yOffset, which might double the offset if caretYOffset already accounts for it. This could lead to incorrect caret position reporting. Fix: Review the yOffset calculation and adjust the end position's y-coordinate calculation to ensure it's accurate without double-counting the offset.
Potential crash when textInput is not a UITextField Why: The caretYOffset function assumes textInput is a UITextField, which might not always be the case. If textInput is a UITextView, it could cause a runtime crash when trying to cast it to UITextField. Fix: Modify caretYOffset to handle both UITextField and UITextView appropriately or add a check to ensure the cast is valid before accessing properties specific to UITextField.
yOffset is only the origin of textInputView inside the field, so adding it to the caret's bottom (origin.y + size.height) moves that bottom into the field's coordinates; it's the same as textField.textInputView.convert(caretRectEnd, to: textField).maxY. Start and end both move by exactly the top padding, and the caret height doesn't change (example app: end.y 27.3 → 57.3 with paddingTop: 30).
as? is a conditional cast: for a UITextView or any other UITextInput it returns nil, the guard returns 0, and multiline inputs keep reporting what they do today. It can't crash.
Yes @ErickValdivia it's 14m model, so it's not very smart 🤓
Main thing is e2e tests here, they matter more 😅
This branch has not been deployed
No deployments
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
📜 Description
Report the caret
yof a single-lineTextInputon iOS from the field's top, as Android does, soKeyboardAwareScrollViewscrolls a padded single-line input fully above the keyboard.💡 Motivation and Context
textSelection(in:)sendscaretRect(for:)as it is. That rect is intextInputView's coordinates, and for aUITextFieldthat's the inner text area, which React Native places inside the field'spaddingTop. So the caret of a single-line input came out one top padding higher than it is.KeyboardAwareScrollViewaddsselection.end.yto the input'sabsoluteY(updateLayoutFromSelection) and scrolls until that point clears the keyboard plusbottomOffset. With the caret reported too high, it stopped one top padding short: withpaddingTop: 30andbottomOffset: 20, the typed text ended up under the keyboard.Android already measures from the field's top:
KeyboardControllerSelectionWatcheraddspaddingTop(and the gravity offset) to the line's top and bottom.UITextFieldchanges.caretYOffsetreturns0for aUITextView, so multiline inputs keep reporting what they do today.xis unchanged, matching Android, wherexcomes fromgetPrimaryHorizontalwithout the horizontal padding.📢 Changelog
iOS
yof single-line inputs (UITextField) from the field's top, adding the offset oftextInputViewinside the field🤔 How Has This Been Tested?
exampleapp (Paper, RN 0.81.4), iPhone 16 simulator, iOS 18.6, on-screen keyboard. The "Aware scroll view" screen temporarily replaced with single-line inputs (height: 70, paddingTop: 30,bottomOffset={20}; snippet below), with an on-screen readout ofselection.end.yfromuseFocusedInputHandler. Focused#5and typed "hello":selection.end.y= 27.3 and the text sits under the keyboardselection.end.y= 57.3 (+30, the top padding) and the text stays above the keyboardpaddingTop: 18went from 35.3 to 53.3 and now clears the keyboard. In a@gorhom/bottom-sheetwithKeyboardAwareScrollView, a single-line input now scrolls by its top padding more (68pt instead of 50pt) and stays visible; multiline inputs don't change.KCTextInputCompositeDelegate.swiftisn't compiled intoKeyboardControllerNativeTests; happy to add it there with a test if you'd like.Repro screen (not part of this PR)
📸 Screenshots (if appropriate):
Before (
main): the caret is reported at 27.3 and "hello" sits under the keyboard:before.mp4
After: 57.3, and "hello" stays above the keyboard:
after.mp4
📝 Checklist