Skip to content

fix: do not overwrite external inputAccessoryView on Fabric#48339

Closed
kirillzyusko wants to merge 1 commit intofacebook:mainfrom
kirillzyusko:fix/do-not-overwrite-external-input-accessory-views-on-fabric
Closed

fix: do not overwrite external inputAccessoryView on Fabric#48339
kirillzyusko wants to merge 1 commit intofacebook:mainfrom
kirillzyusko:fix/do-not-overwrite-external-input-accessory-views-on-fabric

Conversation

@kirillzyusko
Copy link
Copy Markdown
Contributor

@kirillzyusko kirillzyusko commented Dec 19, 2024

Summary:

If 3rd party libs are using inputAccessoryView - the current code can easily break it. Whenever props gets changed we call setDefaultInputAccessoryView which will simply overwrite the current inputAccessoryView (which is highly undesirable).

The same fix on paper was made ~7 years ago: bf36983

Changelog:

[IOS] [FIXED] - Fixed problem with accessory view & 3rd party libs

Test Plan:

Make sure inputAccessoryView functionality works as before

@facebook-github-bot facebook-github-bot added CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. labels Dec 19, 2024
@kirillzyusko
Copy link
Copy Markdown
Contributor Author

@cipolleschi sorry to tag you, but I know you do a lot of iOS stuff in react-native, so wanted to kindly ask you to review this PR 🙏 👀

Copy link
Copy Markdown
Contributor

@cipolleschi cipolleschi left a comment

Choose a reason for hiding this comment

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

This fix looks good. Thank you for taking care of it!

@facebook-github-bot
Copy link
Copy Markdown
Contributor

@cipolleschi has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.

@facebook-github-bot facebook-github-bot added the Merged This PR has been merged. label Dec 19, 2024
@facebook-github-bot
Copy link
Copy Markdown
Contributor

@cipolleschi merged this pull request in 5fc5827.

@react-native-bot
Copy link
Copy Markdown
Collaborator

This pull request was successfully merged by @kirillzyusko in 5fc5827

When will my fix make it into a release? | How to file a pick request?

robhogan pushed a commit that referenced this pull request Dec 30, 2024
Summary:
If 3rd party libs are using `inputAccessoryView` - the current code can easily break it. Whenever props gets changed we call `setDefaultInputAccessoryView` which will simply overwrite the current `inputAccessoryView` (which is highly undesirable).

The same fix on paper was made ~7 years ago: bf36983

## Changelog:

<!-- Help reviewers and the release process by writing your own changelog entry.

Pick one each for the category and type tags:

[IOS] [FIXED] - Fixed problem with accessory view & 3rd party libs

For more details, see:
https://reactnative.dev/contributing/changelogs-in-pull-requests

Pull Request resolved: #48339

Test Plan: Make sure `inputAccessoryView` functionality works as before

Reviewed By: javache

Differential Revision: D67451188

Pulled By: cipolleschi

fbshipit-source-id: bc3fa82ae15f8acedfd0b4e17bdea69cbd8c8a8d
@react-native-bot
Copy link
Copy Markdown
Collaborator

This pull request was successfully merged by @kirillzyusko in d34032b

When will my fix make it into a release? | How to file a pick request?

kirillzyusko added a commit to kirillzyusko/react-native-keyboard-controller that referenced this pull request Jan 15, 2025
## 📜 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:


![image](https://github.com/user-attachments/assets/06f85e15-9347-4569-b6a6-06018d61231f)

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/AMKeyboardFrameTracker

Closes
#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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Merged This PR has been merged. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants