Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: keyboard offset on iOS #727

Merged
merged 50 commits into from
Jan 15, 2025
Merged

feat: keyboard offset on iOS #727

merged 50 commits into from
Jan 15, 2025

Conversation

kirillzyusko
Copy link
Owner

@kirillzyusko kirillzyusko commented Dec 12, 2024

📜 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

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

Docs

  • mention that KeyboardGestureArea is not Android specific anymore;
  • add new textInputNativeID description + show how to use it.

JS

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):

ScreenRecording_01-15-2025.12-54-13.PM_1.MP4

📝 Checklist

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

@kirillzyusko kirillzyusko added documentation Improvements or additions to documentation 🍎 iOS iOS specific 👆 interactive keyboard Anything related to interactive keyboard dismissing 🚨 requires API changes 🚨 Changes that requires changes in library API labels Dec 12, 2024
@kirillzyusko kirillzyusko self-assigned this Dec 12, 2024
Copy link
Contributor

github-actions bot commented Dec 12, 2024

PR Preview Action v1.6.0
Preview removed because the pull request was closed.
2025-01-15 12:09 UTC

Copy link
Contributor

github-actions bot commented Dec 12, 2024

📊 Package size report

Current size Target Size Difference
167188 bytes 164281 bytes 2907 bytes 📈

Copy link

argos-ci bot commented Dec 12, 2024

The latest updates on your projects. Learn more about Argos notifications ↗︎

Build Status Details Updated (UTC)
default (Inspect) 👍 Changes approved 77 changed Jan 15, 2025, 9:29 AM

@kirillzyusko kirillzyusko force-pushed the feat/keyboard-offset-ios branch from f2de5a9 to cee3449 Compare January 14, 2025 15:31
@kirillzyusko kirillzyusko marked this pull request as ready for review January 15, 2025 10:00
@kirillzyusko kirillzyusko merged commit 6da1bb4 into main Jan 15, 2025
30 checks passed
@kirillzyusko kirillzyusko deleted the feat/keyboard-offset-ios branch January 15, 2025 12:08
@kirillzyusko kirillzyusko mentioned this pull request Jan 21, 2025
2 tasks
kirillzyusko added a commit that referenced this pull request Jan 21, 2025
## 📜 Description

Fixed newly discovered warnings.

## 💡 Motivation and Context

In paper example I've seen even more warnings and fixed them as well 🙂
initially these warning were added in
#727

Follow up for
#766

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

- fixed 2 warnings in `KeyboardMovementObserver`;

## 🤔 How Has This Been Tested?

Tested manually.

## 📝 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
documentation Improvements or additions to documentation 👆 interactive keyboard Anything related to interactive keyboard dismissing 🍎 iOS iOS specific 🚨 requires API changes 🚨 Changes that requires changes in library API
Projects
None yet
1 participant