Skip to content

Instantly share code, notes, and snippets.

@ryanlintott
Last active May 13, 2024 07:20
Show Gist options
  • Save ryanlintott/b57b975c8d2e3cad0353a6b6e9d49928 to your computer and use it in GitHub Desktop.
Save ryanlintott/b57b975c8d2e3cad0353a6b6e9d49928 to your computer and use it in GitHub Desktop.
A SwiftUI environment value for the keyboard height that updates with animation. This is useful when you want a specific view in a stack to stick to the bottom of the keyboard when the keyboard moves up.
import SwiftUI
struct ContentView: View {
var body: some View {
KeyboardAvoidingWithOffset()
.keyboardHeightEnvironmentValue()
}
}
struct KeyboardAvoidingWithOffset: View {
@Environment(\.keyboardHeight) var keyboardHeight
@State private var text = ""
var body: some View {
VStack {
TextField("Hello World", text: $text)
.textFieldStyle(.roundedBorder)
Color.blue.opacity(0.5)
.frame(height: keyboardHeight > 0 ? keyboardHeight : 100)
}
.frame(maxHeight: .infinity, alignment: .bottom)
.ignoresSafeArea(.keyboard)
}
}
#Preview {
ContentView()
}
//
// KeyboardHeightEnvironmentValue.swift
// KeyboardAvoidance
//
// Created by Ryan Lintott on 2024-02-01.
//
import SwiftUI
private struct KeyboardHeightEnvironmentKey: EnvironmentKey {
static let defaultValue: CGFloat = 0
}
extension EnvironmentValues {
/// Height of software keyboard when visible
var keyboardHeight: CGFloat {
get { self[KeyboardHeightEnvironmentKey.self] }
set { self[KeyboardHeightEnvironmentKey.self] = newValue }
}
}
struct KeyboardHeightEnvironmentValue: ViewModifier {
@State private var keyboardHeight: CGFloat = 0
func body(content: Content) -> some View {
content
.environment(\.keyboardHeight, keyboardHeight)
/// Approximation of Apple's keyboard animation
/// source: https://forums.developer.apple.com/forums/thread/48088
.animation(.interpolatingSpring(mass: 3, stiffness: 1000, damping: 500, initialVelocity: 0), value: keyboardHeight)
.background {
GeometryReader { keyboardProxy in
GeometryReader { proxy in
Color.clear
.onChange(of: keyboardProxy.safeAreaInsets.bottom - proxy.safeAreaInsets.bottom) { newValue in
DispatchQueue.main.async {
if keyboardHeight != newValue {
keyboardHeight = newValue
}
}
}
}
.ignoresSafeArea(.keyboard)
}
}
}
}
public extension View {
/// Adds an environment value for software keyboard height when visible
///
/// Must be applied on a view taller than the keyboard that touches the bottom edge of the safe area.
/// Access keyboard height in any child view with
/// @Environment(\.keyboardHeight) var keyboardHeight
func keyboardHeightEnvironmentValue() -> some View {
#if os(iOS)
modifier(KeyboardHeightEnvironmentValue())
#else
environment(\.keyboardHeight, 0)
#endif
}
}
#Preview {
VStack {
TextField("Example", text: .constant(""))
}
.keyboardHeightEnvironmentValue()
}
@ryanlintott
Copy link
Author

2024-02-01 18 20 02

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment