Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/verify-ios.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,5 +77,5 @@ jobs:
test \
-scheme KeyboardControllerNative \
-only-testing KeyboardControllerNativeTests \
-destination 'platform=iOS Simulator,name=iPhone 16 Pro,OS=18.4' \
-destination 'platform=iOS Simulator,name=iPhone 16 Pro,OS=18.5' \
CODE_SIGNING_ALLOWED=NO | xcpretty"
24 changes: 12 additions & 12 deletions FabricExample/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2597,7 +2597,7 @@ PODS:
- ReactCommon/turbomodule/core
- SocketRocket
- Yoga
- RNReanimated (4.1.2):
- RNReanimated (4.2.1):
- boost
- DoubleConversion
- fast_float
Expand All @@ -2624,11 +2624,11 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- RNReanimated/reanimated (= 4.1.2)
- RNReanimated/reanimated (= 4.2.1)
- RNWorklets
- SocketRocket
- Yoga
- RNReanimated/reanimated (4.1.2):
- RNReanimated/reanimated (4.2.1):
- boost
- DoubleConversion
- fast_float
Expand All @@ -2655,11 +2655,11 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- RNReanimated/reanimated/apple (= 4.1.2)
- RNReanimated/reanimated/apple (= 4.2.1)
- RNWorklets
- SocketRocket
- Yoga
- RNReanimated/reanimated/apple (4.1.2):
- RNReanimated/reanimated/apple (4.2.1):
- boost
- DoubleConversion
- fast_float
Expand Down Expand Up @@ -2748,7 +2748,7 @@ PODS:
- ReactCommon/turbomodule/core
- SocketRocket
- Yoga
- RNWorklets (0.6.0):
- RNWorklets (0.7.1):
- boost
- DoubleConversion
- fast_float
Expand All @@ -2775,10 +2775,10 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- RNWorklets/worklets (= 0.6.0)
- RNWorklets/worklets (= 0.7.1)
- SocketRocket
- Yoga
- RNWorklets/worklets (0.6.0):
- RNWorklets/worklets (0.7.1):
- boost
- DoubleConversion
- fast_float
Expand All @@ -2805,10 +2805,10 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- RNWorklets/worklets/apple (= 0.6.0)
- RNWorklets/worklets/apple (= 0.7.1)
- SocketRocket
- Yoga
- RNWorklets/worklets/apple (0.6.0):
- RNWorklets/worklets/apple (0.7.1):
- boost
- DoubleConversion
- fast_float
Expand Down Expand Up @@ -3184,9 +3184,9 @@ SPEC CHECKSUMS:
RNCMaskedView: 63268de1986a098b5f4d1fb5b1bc1e97fade0aee
RNGestureHandler: 4f7cc97a71d4fe0fcba38c94acdd969f5f17c91c
RNReactNativeHapticFeedback: 63aa39dbd6ef56e9de688210c5761d4007927a7e
RNReanimated: b8b86b99a4bb938cacac1213724cb4c8edb6c614
RNReanimated: f2cdef8c5ec70e440498b949f9af3ea39f70fcec
RNScreens: 74985ca8e102294a60cec7513fa84c936fa0b20b
RNWorklets: e752b7443b51916158d3b6ca6700294827e8a1ea
RNWorklets: ab7740c2a152f77ff76a40d52be860d8f128fab1
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
Yoga: 922d794dce2af9c437f864bf4093abfa7a131adb

Expand Down
4 changes: 4 additions & 0 deletions FabricExample/jestSetup.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import "react-native-gesture-handler/jestSetup";

require("react-native-reanimated/lib/module/jestUtils").setUpTests();

jest.mock("react-native-worklets", () =>
require("react-native-worklets/src/mock"),
);

jest.mock("react-native-keyboard-controller", () =>
require("react-native-keyboard-controller/jest"),
);
Expand Down
4 changes: 2 additions & 2 deletions FabricExample/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@
"react-native-gesture-handler": "2.28.0",
"react-native-haptic-feedback": "2.3.3",
"react-native-keyboard-controller": "link:../",
"react-native-reanimated": "4.1.2",
"react-native-reanimated": "4.2.1",
"react-native-safe-area-context": "5.6.1",
"react-native-screens": "4.16.0",
"react-native-toast-message": "^2.3.0",
"react-native-web": "^0.21.2",
"react-native-webview": "^13.16.0",
"react-native-worklets": "^0.6.0"
"react-native-worklets": "^0.7.1"
},
"devDependencies": {
"@babel/core": "^7.25.2",
Expand Down
181 changes: 143 additions & 38 deletions FabricExample/yarn.lock

Large diffs are not rendered by default.

11 changes: 10 additions & 1 deletion ios/animations/KeyboardAnimation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,18 @@ public class KeyboardAnimation: KeyboardAnimationProtocol {
// constructor variables
let fromValue: Double
let toValue: Double
let duration: Double
let speed: Double
let timestamp: CFTimeInterval
// internal state
var lastValue: Double

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
lastValue = fromValue
speed = Double(animation.speed)
timestamp = CACurrentMediaTime()
}
Expand All @@ -48,6 +53,10 @@ public class KeyboardAnimation: KeyboardAnimationProtocol {
return fromValue < toValue
}

var isFinished: Bool {
return lastValue == toValue
}

func valueAt(time _: Double) -> Double {
fatalError("Method is not implemented in abstract class!")
}
Expand Down
2 changes: 1 addition & 1 deletion ios/animations/SpringAnimation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion ios/animations/TimingAnimation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions ios/extensions/Notification.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ extension Notification {

extension Notification.Name {
static let shouldIgnoreKeyboardEvents = Notification.Name("shouldIgnoreKeyboardEvents")
static let keyboardDidAppear = Notification.Name("keyboardDidAppear")
}
2 changes: 1 addition & 1 deletion ios/interactive/KeyboardAreaExtender.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class KeyboardAreaExtender: NSObject {
NotificationCenter.default.addObserver(
self,
selector: #selector(keyboardDidAppear),
name: UIResponder.keyboardDidShowNotification,
name: .keyboardDidAppear,
object: nil
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -30,6 +30,11 @@ extension KeyboardMovementObserver {
guard !UIResponder.isKeyboardPreloading else { return }
let (duration, _) = notification.keyboardMetaData()
tag = UIResponder.current.reactViewTag
self.notification = notification
// when keyboard disappears immediately replace the keyboard frame with .zero
// since this will be the final frame of the keyboard after animation
self.notification?.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] =
NSValue(cgRect: CGRect(x: 0, y: 0, width: 0, height: 0))
self.duration = duration

onRequestAnimation()
Expand All @@ -44,10 +49,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
Expand All @@ -57,9 +60,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)

Expand All @@ -70,6 +71,10 @@ extension KeyboardMovementObserver {
removeKeyboardWatcher()
setupKVObserver()
animation = nil

NotificationCenter.default.post(
name: .keyboardDidAppear, object: notification, userInfo: nil
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ extension KeyboardMovementObserver {
prevKeyboardPosition = keyboardPosition

if let animation = animation {
if animation.isFinished {
return
}
let baseDuration = animation.timingAt(value: keyboardPosition)

#if targetEnvironment(simulator)
Expand All @@ -59,6 +62,7 @@ extension KeyboardMovementObserver {
// but CASpringAnimation can never get to this final destination
let race: (CGFloat, CGFloat) -> CGFloat = animation.isIncreasing ? max : min
keyboardPosition = race(position, keyboardPosition)
animation.lastValue = keyboardPosition
}

onEvent(
Expand Down
54 changes: 51 additions & 3 deletions ios/observers/movement/observer/KeyboardMovementObserver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,30 @@ public class KeyboardMovementObserver: NSObject {
var onCancelAnimation: () -> Void
// progress tracker
@objc public 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!
Expand All @@ -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,
Expand Down
Loading