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
## 📜 Description
Added an ability to specify `offset` for interactive keyboard dismissal
on iOS.
## 💡 Motivation and Context
In this PR I'm exposing `KeyboardGestureArea` on iOS and adding two
props for that: `offset` and `textInputNativeID`.
This PR is a re-thinking concept of how we work with
`inputAccessoryView` on iOS.
To make long text short - default `InputAccessoryView` comes with many
restrictions, such as not growing `TextInput`, unability to specify
position on the screen, weird animations on unmount, complexity with
managing `SafeArea` insets, etc.
We already have `KeyboardStickyView` that don't have all that problems,
but if you interactively dismiss a keyboard then interactive dismissal
starts from a top border of the keyboard (not the input).
Taking a step back and utilising `inputAccessoryView` (moving a view
from RN hierarchy directly into `inputAccessoryView`) is possible, but
comes with a previous set of challenges.
In this PR I decided to think about different concepts between
iOS/Android and how to make a solution that will work everywhere
identically. And the idea is to create an invisible/non-interactable
instance of `inputAccessoryView`, that will simply extend the keyboard
area (but keyboard-controller will know about that offset and will
automatically exclude it from final keyboard dimensions, so you can use
everything as you used before).
Schematically all process can be shown on a diagram below:

However new approach comes with its own set of challenges. Mainly they
come from the fact how keyboard dismissal works on iOS, and in simple
words:
- when you perform `Keyboard.dismiss()`/press enter then whole
combination (keyboard + inputAccessoryView) is treated as a single
keyboard and entire element gets hidden in a single animation.
- when you perform interactive dismissal, then we have two fold
animation - first we dismiss a keyboard, and in second stage we dismiss
`inputAccessoryView`.
From all the description above it's clear, that we want to ignore
`inputAccessoryView` animations or exclude its height from the animation
(when its animated as a single element).
To solve the first problem (when keyboard dismissed as a single element)
we need to remove `inputAccessoryView` and only then perform an
animation. Otherwise if we use default hooks
`useKeyboardAnimation`/`useReanimatedKeyboardAnimation` that rely on
layout animation, then we will see unsynchronized animation (because for
example actual keyboard height is 250 + 50, but in JS we give only value
of 250, so we will animate from 250 to 0, though actual animation will
be from 300 to 0). To fix that I had to swizzle into
`resignFirstResponder`. In this method we see, if we have
`InvisibleAccessoryView`, then we postpone a keyboard dismissal and
remove current `inputAccessoryView`. In this case we will dismiss a
keyboard without `inputAccessoryView`, so it will work as it works
before.
The second main challenge was a time when to remove `inputAccessoryView`
during interactive keyboard dismissal. The initial idea was to remove it
as soon as dismiss gesture begins. However I rejected this idea in
d11afd6
mainly because it was causing a lot of issues (such as ghost animation
when keyboard is fully dismissed). When we remove that code it removes
additional complexity and we remove `inputAccessoryView` when we call
`resignFirstResponder` (happens when keyboard gets dismissed, i. e.
first phase passed). In this case it works more predictable.
Last but not least - it's wort to note, that the idea with invisible
`inputAccessoryView` is not new in iOS community, and some even native
projects are utilizing it:
https://github.com/iAmrMohamed/AMKeyboardFrameTrackerCloses#250
## 📢 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 -->
### Docs
- mention that `KeyboardGestureArea` is not Android specific anymore;
- add new `textInputNativeID` description + show how to use it.
### JS
- don't exclude `iOS` for `KeyboardGestureArea` in codegen;
- expose new `textInputNativeID` property for `KeyboardGestureArea`;
- applied patch in fabric example app
facebook/react-native#48339
- make `interpolator` optional (will be `ios` on `iOS` and `linear` on
`Android`)
- make growing/multiline `TextInput` in interactive iOS keyboard
example;
### iOS
- expose `KeyboardGestureArea` on iOS as well
- added `InvisibleInputAccessoryView` class;
- added `KeyboardEventsIgnorer` class;
- added `KeyboardAreaExtender` class;
- added `KeyboardOffsetProvider` class;
- added `KeyboardEventsIgnorer` class;
- added `UIResponderSwizzle` class;
- added `shouldIgnoreKeyboardEvents` event to `Notification`;
- added `nativeID` extension to `UIResponder` (and it's mock for a
native project);
### Android
- added no-op setters for `textInputNativeId`;
## 🤔 How Has This Been Tested?
Tested locally on:
- iPhone 6s (iOS 15.8, real device);
- iPhone 11 (iOS 18.0, iOS 18.1, real device)
- iPhone 16 Pro (iOS 18.0, simulator)
- iPhone 15 Pro (iOS 17.5, simulator)
- iPhone 14 Pro (iOS 16.5, simulator)
## 📸 Screenshots (if appropriate):
https://github.com/user-attachments/assets/097d76e1-4f79-4a27-89b7-43a479b6b32b
## 📝 Checklist
- [x] CI successfully passed
- [x] I added new mocks and corresponding unit-tests if library API was
changed
-**ios** - interactive keyboard dismissing will work as in iOS: swipes in non-keyboard area will not affect keyboard positioning, but if your swipe touches keyboard - keyboard will follow finger position.
36
30
-**linear** - gestures inside the component will linearly affect the position of the keyboard, i.e. if the user swipes down by 20 pixels, then the keyboard will also be moved down by 20 pixels, even if the gesture was not made over the keyboard area.
A boolean prop which allows to customize interactive keyboard behavior. If set to `true` then it allows to show keyboard (if it's already closed) by swipe up gesture. `false` by default.
A boolean prop which allows to customize interactive keyboard behavior. If set to `false`, then any gesture will not affect keyboard position if the keyboard is shown. `true` by default.
0 commit comments