KeyboardObserver
Observes keyboard visibility and frame changes via UIResponder keyboard notifications.
Model
Use KeyboardState.hidden as a default when initializing state.
public struct KeyboardState: Sendable, Equatable {
/// Whether the keyboard is currently visible.
public let isVisible: Bool
/// The height of the keyboard in points. Zero when hidden.
public let height: CGFloat
/// The system animation duration for the keyboard transition.
public let animationDuration: TimeInterval
public static let hidden = KeyboardState(
isVisible: false, height: 0, animationDuration: 0.25
)
} Protocol
public protocol KeyboardObserving: Sendable {
/// The current keyboard state.
var state: KeyboardState { get }
/// An AsyncStream that emits keyboard state changes.
var stateStream: AsyncStream<KeyboardState> { get }
} Usage
let keyboard = KeyboardObserver()
// Check if keyboard is visible
if keyboard.state.isVisible {
print("Keyboard height: \(keyboard.state.height)")
}
// Adjust layout when keyboard appears
for await state in keyboard.stateStream {
withAnimation(.easeOut(duration: state.animationDuration)) {
keyboardHeight = state.height
}
} Testing
The observer accepts an injected NotificationCenter so tests can post synthetic keyboard notifications with fake frames and durations.
import UIKit
import ForgeObservers
@Test
func keyboardObserverParsesFrame() async throws {
let center = NotificationCenter()
let observer = KeyboardObserver(notificationCenter: center)
let frame = CGRect(x: 0, y: 600, width: 390, height: 291)
center.post(
name: UIResponder.keyboardWillShowNotification,
object: nil,
userInfo: [
UIResponder.keyboardFrameEndUserInfoKey: frame,
UIResponder.keyboardAnimationDurationUserInfoKey: TimeInterval(0.3),
]
)
try await Task.sleep(for: .milliseconds(50))
#expect(observer.state.isVisible == true)
#expect(observer.state.height == 291)
#expect(observer.state.animationDuration == 0.3)
} SwiftUI note -- SwiftUI provides @FocusState and keyboard avoidance out of the box. This observer is most useful in ViewModels, non-UI code, or when you need precise control over keyboard animation coordination.