Every now and then, I’m frustrated that SwiftUI is missing an obvious environment variable.
I really expected it to have@Environment(.keyboardShowing) var keyboardShowing
It doesn’t. But you can make your own.
Simple example of how you use it here:
@main
struct KeyboardIndicatorApp: App {
var body: some Scene {
WindowGroup {
ContentView()
// Simple modifier injects the new environment variable
.addKeyboardVisibilityToEnvironment()
}
}
}
struct ContentView: View {
//You can then read it in any view
@Environment(\.keyboardShowing) var keyboardShowing
@FocusState private var isTextFieldFocused: Bool
@State private var text:String = ""
var body: some View {
TextField("Type Something...", text: $text)
.focused($isTextFieldFocused)
.safeAreaInset(edge: .trailing) {
//Button shows if keyboard is showing
Button {
isTextFieldFocused = false
} label: {
Image(systemName: "keyboard.chevron.compact.down")
}
.opacity(keyboardShowing ? 1 : 0)
}
.padding()
}
}
In this example, we show a ‘keyboard down’ button if the keyboard is showing.
The button clears focus and hides the keyboard.
Note – this is a VERY contrived example. In this simple case, you could just use the @FocusState for the same job.
In my use case, the ‘hide keyboard’ button and the text field are in different parts of my view tree – so using the environment variable avoids having to create a more complex data-passing solution.
The implementation is based on this Stack Overflow answer, but with the addition of wrapping the publisher up into a view modifier, and using that to set an environment variable.
public extension View {
/// Sets an environment value for keyboardShowing
/// Access this in any child view with
/// @Environment(\.keyboardShowing) var keyboardShowing
/// You should add the modifier near the top of your view hierarchy (to RootView / ContentView or similar)
/// On MacOS, this is always false
func addKeyboardVisibilityToEnvironment() -> some View {
modifier(KeyboardVisibility())
}
}
private struct KeyboardShowingEnvironmentKey: EnvironmentKey {
static let defaultValue: Bool = false
}
public extension EnvironmentValues {
var keyboardShowing: Bool {
get { self[KeyboardShowingEnvironmentKey.self] }
set { self[KeyboardShowingEnvironmentKey.self] = newValue }
}
}
private struct KeyboardVisibility:ViewModifier {
#if os(macOS)
fileprivate func body(content: Content) -> some View {
content
.environment(\.keyboardShowing, false)
}
#else
@State var isKeyboardShowing:Bool = false
private var keyboardPublisher: AnyPublisher<Bool, Never> {
Publishers
.Merge(
NotificationCenter
.default
.publisher(for: UIResponder.keyboardWillShowNotification)
.map { _ in true },
NotificationCenter
.default
.publisher(for: UIResponder.keyboardDidHideNotification)
.map { _ in false })
.debounce(for: .seconds(0.1), scheduler: RunLoop.main)
.eraseToAnyPublisher()
}
fileprivate func body(content: Content) -> some View {
content
.environment(\.keyboardShowing, isKeyboardShowing)
.onReceive(keyboardPublisher) { value in
isKeyboardShowing = value
}
}
#endif
}
Creating our own environment value is very simple, and wrapping the whole thing up in a view modifier makes it really clean to add to a project.
I have added this to my HSSwiftUI package of useful SwiftUI extensions.





















