From 673ba8fb9a31302bbc00d4b76a33d35eb54b8356 Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Mon, 4 Aug 2025 11:14:39 +0200 Subject: [PATCH 1/3] feat: manual `did` events on iOS --- ios/animations/KeyboardAnimation.swift | 4 +++- ios/animations/SpringAnimation.swift | 2 +- ios/animations/TimingAnimation.swift | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/ios/animations/KeyboardAnimation.swift b/ios/animations/KeyboardAnimation.swift index fbf557f30c..83b62f9386 100644 --- a/ios/animations/KeyboardAnimation.swift +++ b/ios/animations/KeyboardAnimation.swift @@ -23,13 +23,15 @@ public class KeyboardAnimation: KeyboardAnimationProtocol { // constructor variables let fromValue: Double let toValue: Double + let duration: Double let speed: Double let timestamp: CFTimeInterval - init(fromValue: Double, toValue: Double, animation: CAMediaTiming) { + init(fromValue: Double, toValue: Double, animation: CAMediaTiming, duration: Double) { self.fromValue = fromValue self.toValue = toValue self.animation = animation + self.duration = duration speed = Double(animation.speed) timestamp = CACurrentMediaTime() } diff --git a/ios/animations/SpringAnimation.swift b/ios/animations/SpringAnimation.swift index 8712c29aa7..8e7e388fad 100644 --- a/ios/animations/SpringAnimation.swift +++ b/ios/animations/SpringAnimation.swift @@ -57,7 +57,7 @@ public final class SpringAnimation: KeyboardAnimation { bUnder = 0 } - super.init(fromValue: fromValue, toValue: toValue, animation: animation) + super.init(fromValue: fromValue, toValue: toValue, animation: animation, duration: animation.settlingDuration) } // public functions diff --git a/ios/animations/TimingAnimation.swift b/ios/animations/TimingAnimation.swift index 7120bdb471..7515cb280b 100644 --- a/ios/animations/TimingAnimation.swift +++ b/ios/animations/TimingAnimation.swift @@ -30,7 +30,7 @@ public final class TimingAnimation: KeyboardAnimation { self.p1 = p1 self.p2 = p2 - super.init(fromValue: fromValue, toValue: toValue, animation: animation) + super.init(fromValue: fromValue, toValue: toValue, animation: animation, duration: animation.duration) } // MARK: public functions From f6dba82d9685078f13f89dc93bc235e0ddc84393 Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Tue, 5 Aug 2025 11:21:26 +0200 Subject: [PATCH 2/3] fix: interactive dismissal --- ios/extensions/Notification.swift | 1 + ios/interactive/KeyboardAreaExtender.swift | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ios/extensions/Notification.swift b/ios/extensions/Notification.swift index 9874fd2296..17f0b88edf 100644 --- a/ios/extensions/Notification.swift +++ b/ios/extensions/Notification.swift @@ -18,4 +18,5 @@ extension Notification { extension Notification.Name { static let shouldIgnoreKeyboardEvents = Notification.Name("shouldIgnoreKeyboardEvents") + static let keyboardDidAppear = Notification.Name("keyboardDidAppear") } diff --git a/ios/interactive/KeyboardAreaExtender.swift b/ios/interactive/KeyboardAreaExtender.swift index 8948f11ca0..124304d3bb 100644 --- a/ios/interactive/KeyboardAreaExtender.swift +++ b/ios/interactive/KeyboardAreaExtender.swift @@ -15,7 +15,7 @@ class KeyboardAreaExtender: NSObject { NotificationCenter.default.addObserver( self, selector: #selector(keyboardDidAppear), - name: UIResponder.keyboardDidShowNotification, + name: .keyboardDidAppear, object: nil ) } From e0e43f57e5c675428b10604946d982e34480ac7f Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Wed, 15 Oct 2025 10:50:26 +0200 Subject: [PATCH 3/3] fix: e2e tests --- .../KeyboardMovementObserver+Lifecycle.swift | 12 ----- .../KeyboardMovementObserver+Listeners.swift | 15 +++--- .../observer/KeyboardMovementObserver.swift | 54 +++++++++++++++++-- 3 files changed, 60 insertions(+), 21 deletions(-) diff --git a/ios/observers/movement/observer/KeyboardMovementObserver+Lifecycle.swift b/ios/observers/movement/observer/KeyboardMovementObserver+Lifecycle.swift index 3d23f507cd..cdbd63359b 100644 --- a/ios/observers/movement/observer/KeyboardMovementObserver+Lifecycle.swift +++ b/ios/observers/movement/observer/KeyboardMovementObserver+Lifecycle.swift @@ -25,18 +25,6 @@ public extension KeyboardMovementObserver { name: UIResponder.keyboardWillShowNotification, object: nil ) - NotificationCenter.default.addObserver( - self, - selector: #selector(keyboardDidAppear), - name: UIResponder.keyboardDidShowNotification, - object: nil - ) - NotificationCenter.default.addObserver( - self, - selector: #selector(keyboardDidDisappear), - name: UIResponder.keyboardDidHideNotification, - object: nil - ) } @objc func unmount() { diff --git a/ios/observers/movement/observer/KeyboardMovementObserver+Listeners.swift b/ios/observers/movement/observer/KeyboardMovementObserver+Listeners.swift index b6d517c172..4c763e6de6 100644 --- a/ios/observers/movement/observer/KeyboardMovementObserver+Listeners.swift +++ b/ios/observers/movement/observer/KeyboardMovementObserver+Listeners.swift @@ -14,8 +14,8 @@ extension KeyboardMovementObserver { tag = UIResponder.current.reactViewTag let keyboardHeight = keyboardFrame.cgRectValue.size.height self.keyboardHeight = keyboardHeight + self.notification = notification self.duration = duration - didShowDeadline = Date.currentTimeStamp + Int64(duration) onRequestAnimation() onEvent("onKeyboardMoveStart", Float(self.keyboardHeight) as NSNumber, 1, duration as NSNumber, tag) @@ -30,6 +30,9 @@ extension KeyboardMovementObserver { guard !UIResponder.isKeyboardPreloading else { return } let (duration, _) = notification.keyboardMetaData() tag = UIResponder.current.reactViewTag + self.notification = notification + // when keyboard disappears i + self.notification?.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] = NSValue(cgRect: CGRect(x: 0, y: 0, width: 0, height: 0)) self.duration = duration onRequestAnimation() @@ -44,10 +47,8 @@ extension KeyboardMovementObserver { @objc func keyboardDidAppear(_ notification: Notification) { guard !UIResponder.isKeyboardPreloading else { return } - let timestamp = Date.currentTimeStamp let (duration, frame) = notification.keyboardMetaData() if let keyboardFrame = frame { - let (position, _) = keyboardTrackingView.view.frameTransitionInWindow let keyboardHeight = keyboardFrame.cgRectValue.size.height tag = UIResponder.current.reactViewTag self.keyboardHeight = keyboardHeight @@ -57,9 +58,7 @@ extension KeyboardMovementObserver { return } - // if the event is caught in between it's highly likely that it could be a "resize" event - // so we just read actual keyboard frame value in this case - let height = timestamp >= didShowDeadline ? self.keyboardHeight : position - KeyboardAreaExtender.shared.offset + let height = self.keyboardHeight - KeyboardAreaExtender.shared.offset // always limit progress to the maximum possible value let progress = min(height / self.keyboardHeight, 1.0) @@ -70,6 +69,10 @@ extension KeyboardMovementObserver { removeKeyboardWatcher() setupKVObserver() animation = nil + + NotificationCenter.default.post( + name: .keyboardDidAppear, object: notification, userInfo: nil + ) } } diff --git a/ios/observers/movement/observer/KeyboardMovementObserver.swift b/ios/observers/movement/observer/KeyboardMovementObserver.swift index 2ca88b50cb..67eba6b22c 100644 --- a/ios/observers/movement/observer/KeyboardMovementObserver.swift +++ b/ios/observers/movement/observer/KeyboardMovementObserver.swift @@ -19,7 +19,30 @@ public class KeyboardMovementObserver: NSObject { var onCancelAnimation: () -> Void // progress tracker var keyboardTrackingView = KeyboardTrackingView() - var animation: KeyboardAnimation? + var animation: KeyboardAnimation? { + didSet { + keyboardDidTask?.cancel() + + guard let animation = animation, let notification = notification else { + return + } + + let toValue = animation.toValue + let duration = animation.duration + + let task = DispatchWorkItem { [weak self] in + guard let self = self else { return } + if toValue > 0 { + self.keyboardDidAppear(notification) + } else { + self.keyboardDidDisappear(notification) + } + } + + keyboardDidTask = task + DispatchQueue.main.asyncAfter(deadline: .now() + duration, execute: task) + } + } var prevKeyboardPosition = 0.0 var displayLink: CADisplayLink! @@ -32,9 +55,34 @@ public class KeyboardMovementObserver: NSObject { set { _keyboardHeight = newValue } } - var duration = 0 + var duration = 0 { + didSet { + keyboardDidTask?.cancel() + + guard let notification = notification, + let height = notification.keyboardMetaData().1?.cgRectValue.size.height, duration == 0 + else { + return + } + + let task = DispatchWorkItem { [weak self] in + guard let self = self else { return } + if height > 0 { + self.keyboardDidAppear(notification) + } else { + self.keyboardDidDisappear(notification) + } + } + + keyboardDidTask = task + DispatchQueue.main.asyncAfter(deadline: .now() + UIUtils.nextFrame, execute: task) + } + } + var tag: NSNumber = -1 - var didShowDeadline: Int64 = 0 + // manual did events + var notification: Notification? + var keyboardDidTask: DispatchWorkItem? @objc public init( handler: @escaping (NSString, NSNumber, NSNumber, NSNumber, NSNumber) -> Void,