From ddefb46980ea96a8e26768d44a9fcb0378e4d988 Mon Sep 17 00:00:00 2001 From: Matthew Horan Date: Thu, 28 Nov 2024 00:40:30 -0500 Subject: [PATCH] fix: don't jump to height when iOS keyboard is rapidly opened/closed The interactive dismissal logic would get tripped up if the keyboard was rapidly opened and closed, which can happen if a hardware keyboard is used and the autosuggest dock appears and then disappears when the on screen keyboard is opened. Fixes #6727. --- .../REAKeyboardEventObserver.mm | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/react-native-reanimated/apple/reanimated/apple/keyboardObserver/REAKeyboardEventObserver.mm b/packages/react-native-reanimated/apple/reanimated/apple/keyboardObserver/REAKeyboardEventObserver.mm index 21ca00be810..18b46a63f16 100644 --- a/packages/react-native-reanimated/apple/reanimated/apple/keyboardObserver/REAKeyboardEventObserver.mm +++ b/packages/react-native-reanimated/apple/reanimated/apple/keyboardObserver/REAKeyboardEventObserver.mm @@ -12,6 +12,7 @@ typedef NS_ENUM(NSUInteger, KeyboardState) { OPEN = 2, CLOSING = 3, CLOSED = 4, + OPENING_INTERACTIVE = 6 }; @implementation REAKeyboardEventObserver { @@ -25,6 +26,7 @@ @implementation REAKeyboardEventObserver { float _targetKeyboardHeight; REAUIView *_keyboardView; bool _isKeyboardObserverAttached; + bool _isInteractiveDismissalCanceled; } - (instancetype)init @@ -35,6 +37,7 @@ - (instancetype)init _state = UNKNOWN; _animationStartTimestamp = 0; _isKeyboardObserverAttached = false; + _isInteractiveDismissalCanceled = false; NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; [notificationCenter addObserver:self @@ -213,20 +216,21 @@ - (void)keyboardWillChangeFrame:(NSNotification *)notification } /* - This may seem a bit confusing, but usually, the state should be either OPENED or CLOSED. - However, if it shows as OPENING, it means that the interactive dismissal was canceled. + If interactive dismissal was canceled, beginFrame may not reflect the latest position + of the keyboard. We need to check whether interactive dismissal was canceled, get the + the latest frame directly from the keyboardView, then reset the flag. */ - bool isInteractiveMode = _state == OPENING && _isKeyboardObserverAttached; + bool isInteractiveDismissalCanceled = _isInteractiveDismissalCanceled; auto keyboardView = [self getKeyboardView]; if (_state == CLOSING) { _targetKeyboardHeight = 0; - } else if (isInteractiveMode) { - // This condition can be met after canceling interactive dismissal. + } else if (isInteractiveDismissalCanceled) { _initialKeyboardHeight = windowSize.height - keyboardView.frame.origin.y; + _isInteractiveDismissalCanceled = false; } bool hasKeyboardAnimation = [self hasAnyAnimation:keyboardView]; - if (hasKeyboardAnimation || isInteractiveMode) { + if (hasKeyboardAnimation || isInteractiveDismissalCanceled) { _measuringView.frame = CGRectMake(0, -1, 0, _initialKeyboardHeight); [UIView animateWithDuration:animationDuration animations:^{ @@ -338,8 +342,10 @@ - (void)updateKeyboardHeightDuringInteractiveDismiss:(CGPoint)oldKeyboardFrame return; } float visibleKeyboardHeight = windowHeight - (newKeyboardFrame.y - keyboardHeight / 2); + _isInteractiveDismissalCanceled = false; if (oldKeyboardFrame.y > newKeyboardFrame.y) { _state = OPENING; + _isInteractiveDismissalCanceled = true; } else if (oldKeyboardFrame.y < newKeyboardFrame.y) { _state = CLOSING; }