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.