I've spent a lot of time researching and playing with different codes to make this work but can't seem to figure it out. Below you'll see two pieces of code, one of me creating the "Done" bar button item and the other is checking to see if keyboard is present and to close it when the button is clicked. The one issue I'm having is, I only want the bar button to show when the keyboard is present. I want the bar button hidden once the keyboard is gone or prior to even opening up the keyboard in the first place. How do you do that nowadays with SwiftUI?
.navigationBarItems(trailing:
Button(action: {
if UIApplication.shared.isKeyboardPresented {
UIApplication.shared.endEditing()
}
}, label: {
Text("Done")
})
extension UIApplication {
func endEditing() {
sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
/// Checks if view hierarchy of application contains `UIRemoteKeyboardWindow` if it does, keyboard is presented
var isKeyboardPresented: Bool {
if let keyboardWindowClass = NSClassFromString("UIRemoteKeyboardWindow"),
self.windows.contains(where: { $0.isKind(of: keyboardWindowClass) }) {
return true
} else {
return false
}
}
}
As an option, you can define an object to listen to keyboard notifications:
class KeybordManager: ObservableObject {
static let shared = KeybordManager()
@Published var keyboardFrame: CGRect? = nil
init() {
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(willHide), name: UIResponder.keyboardWillHideNotification, object: nil)
notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
}
@objc func willHide() {
self.keyboardFrame = .zero
}
@objc func adjustForKeyboard(notification: Notification) {
guard let keyboardValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
let keyboardScreenEndFrame = keyboardValue.cgRectValue
self.keyboardFrame = keyboardScreenEndFrame
}
}
Then inside your view subscribe to keyboardFrame
updates and show and hide the Done
button accordingly:
public struct ContentView: View {
@State private var showDoneButton = false
@State private var text = ""
public var body: some View {
NavigationView {
TextField("Some text", text: $text)
.navigationBarItems(trailing:
Group {
if self.showDoneButton {
Button(action: {
UIApplication.shared.endEditing()
}, label: {
Text("Done")
})
} else {
EmptyView()
}
}
)
.onReceive(KeybordManager.shared.$keyboardFrame) { keyboardFrame in
if let keyboardFrame = keyboardFrame, keyboardFrame != .zero {
self.showDoneButton = true
} else {
self.showDoneButton = false
}
}
}
}
}
extension UIApplication {
func endEditing() {
sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}