Skip to content

Conversation

kirillzyusko
Copy link
Owner

πŸ“œ Description

πŸ’‘ Motivation and Context

Closes #973

πŸ“’ Changelog

JS

iOS

Android

πŸ€” How Has This Been Tested?

πŸ“Έ Screenshots (if appropriate):

πŸ“ Checklist

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

Sorry, something went wrong.

@kirillzyusko kirillzyusko self-assigned this Jun 11, 2025
@kirillzyusko kirillzyusko added πŸ› bug Something isn't working 🍎 iOS iOS specific πŸ‘† interactive keyboard Anything related to interactive keyboard dismissing labels Jun 11, 2025
Copy link
Contributor

github-actions bot commented Jun 11, 2025 β€’

πŸ“Š Package size report

Current size Target Size Difference
211898 bytes 211571 bytes 327 bytes πŸ“ˆ

@kirillzyusko kirillzyusko force-pushed the fix/interactive-keyboard-dismissal-ios-26 branch from 1cb8f01 to 819d196 Compare June 13, 2025 07:58
@kirillzyusko kirillzyusko marked this pull request as draft June 13, 2025 09:42

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
@kirillzyusko kirillzyusko force-pushed the fix/interactive-keyboard-dismissal-ios-26 branch from 819d196 to b6b9b9e Compare July 23, 2025 16:56
@kirillzyusko kirillzyusko added the iOS 26 Anything specific to iOS 26 label Jul 25, 2025
@kirillzyusko kirillzyusko mentioned this pull request Jul 31, 2025
2 tasks
kirillzyusko added a commit that referenced this pull request Aug 1, 2025
## πŸ“œ Description

Introduce compat `KeyboardTrackingView` layer.

## πŸ’‘ Motivation and Context

Starting from iOS beta 4 we got breaking changes that breaks
`react-native-keyboard-controller`.

### 1️⃣ `layer.presentation` doesn't update its values during animation

When we read `print("kv:
\(KeyboardViewLocator.shared.resolve()?.layer.presentation()?.frame.origin.y)")`
it will always give us values as "keyboard already opened":

```bash
kv: Optional(528.0)
kv: Optional(528.0)
kv: Optional(528.0)
kv: Optional(528.0)
kv: Optional(528.0)
kv: Optional(528.0)
kv: Optional(528.0)
```

So it will cause "instant" animations and it's not something that
developers expect to see.

### 2️⃣ `layer` doesn't contain the information about its `CAAnimation`

While this problem is not such a critical, because we use fallback
approach: if `animation` is `nil` then we just previous keyboard frame
without additional frame correction. But it'll produce unsynchronized
animation (we literally will come back to
`[email protected]` version or earlier). So
eventually we need to have an access to these variables.

<hr>

It's obvious that we need to get a tracking view to deliver smooth
animations. I had few ideas how to add it:

### 1️⃣ Use `Notification` approach

The first approach was to use keyboard notifications, put tracking view
outside of visible screen area and track it. It works, but it'll give
`CASpringAnimation` after interactive gesture dismissal (when keyboard
goes up). I've tried various ideas, like changing frame without
`UIView.animate` etc., but in all cases I got the same outcome:
- animation can be `nil`;
- animation may be `CASpringAnimation` instead of `CABasicAnimation`.

### 2️⃣ Use `inputAccessoryView` as tracking view

The other idea was to setup listener on `inputAccessoryView`.

In reality this view doesn't have any animation - I assume it's because
of the fact, that it's attached to keyboard and keyboard drives the
animation.

_**Theoretically**_ we could lookup to `superview` and track its
animation, but I didn't verify it - that solution would be pretty
complex because we had to inject `inputAccessoryView` for each
`TextInput`...

### 3️⃣ Use `keyboardLayoutGuide` for creating tracking view

The other idea to try was usage of `keyboardLayoutGuide`. The idea the
same as with `Notifications`. BUT! Fortunately that approach always
gives proper animations and I can see `CABasicAnimation` for interactive
dismissal case. This is how it looks if we make the view visible:


https://github.com/user-attachments/assets/efef1976-1940-4a98-82d9-114f10df4022

The benefit of this approach is that we always can see how a native view
behaves - react-native just replicates the native view.

One limitation is that `keyboardLayoutGuide` is available from iOS 15.
Moreover we use `usesBottomSafeArea` property, which is available
starting from iOS 17. Taking all these facts into consideration and
keeping in mind, that everything was pretty stable until iOS 26, I
decided to use old implementation for all iOS version < 26. I don't want
to break old code, but discovering bugs in new code/iOS is kind of
expected process πŸ˜…

<hr>

With the new concept of a compat layer (`KeyboardTrackingView`) we can
separate concepts and move all interpolation code into
`KeyboardTrackingView` class itself. As a proof of concept I fixed
interactive keyboard dismissal for iOS 26+ - now we can embed all math
for calculating keyboard position into this layer 😎 (so this PR will
automatically close
#975
- note it doesn't mean that I fully fixed interactive dismissal: this PR
is a first step into making `react-native-keyboard-controller` codebase
more predictable/enhanceable etc.):


https://github.com/user-attachments/assets/5dda7a96-42d1-43ad-8cbd-7ca24828d0d1

Closes
#975

## πŸ“’ 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

- created `KeyboardTrackingView` as a compat layer (iOS < 26 and iOS
26+)
- use `KeyboardTrackingView` in `KeyboardMovementObserver` and avoid
direct usages of `keyboardView` instance;
- move interactive interpolation code into `KeyboardTrackingView` from
`KeyboardMovementObserver`.

## πŸ€” How Has This Been Tested?

Tested manually on:
- iPhone 6s (iOS 15.8, real device);
- iPhone 11 (iOS 26, real device)
- iPhone 16 Pro (iOS 26, real device)
- iPhone 14 Pro (iOS 18.4, real device)
- iPhone 15 Pro (iOS 17.5, simulator)
- iPhone 16 Pro (iOS 26, simulator)

## πŸ“Έ Screenshots (if appropriate):

|Before|After|
|-------|-----|
|<video
src="https://github.com/user-attachments/assets/e2c808dc-2aa5-4768-8974-4df429abba57">|<video
src="https://github.com/user-attachments/assets/04448303-be1a-4c0b-b705-fe0c936726ad">|

> captured with a fix for `did` events (dispatch them only after
keyboard finish its animation) for both before/after cases. Captured on
iOS 26 for better proof/visibility.

## πŸ“ 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

πŸ› bug Something isn't working πŸ‘† interactive keyboard Anything related to interactive keyboard dismissing 🍎 iOS iOS specific iOS 26 Anything specific to iOS 26

Projects

None yet

Development

Successfully merging this pull request may close these issues.

iOS 26 doesn't trigger onInteractive handler

1 participant