In short, I’m trying to achieve the Reminders.app interaction when you add an entry, hit [Return] and can then add the next entry immediately (i.e. keyboard does not dismiss, but only does if you hit [Return] on an empty line).
This would be straightforward if it was not for this issue: how can I keep the keyboard opened with @FocusState with SwiftUI without a bounce? (keyboard bounces after a [Return]).
I considered many solutions but the cleanest to keep the keyboard up is to leverage introspect:
class TextFieldKeyboardBehavior: UIView, UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
return false
}
}
struct TestView: View {
@State var text: String = ""
var textFieldKeyboardBehavior = TextFieldKeyboardBehavior()
var body: some View {
VStack {
TextField("Title", text: $text)
.introspect(.textField, on: .iOS(.v17)) {
$0.delegate = textFieldKeyboardBehavior
}
.onSubmit {
// code
}
}
}
}
Now the (big!) problem with this approach is that the onSubmit
never gets called. If I add an onChange
modifier, it is called on every press, but I don’t believe observing value changes here can reliably detect when is the correct moment to actually dismiss the keyboard. I understand I’m effectively disabling [Return] capabilities and so the behaviour should come as no surprise, but then I’m wondering whether there’s another approach leveraging introspect combined with a TextField
that enables what I’m after: 1) keyboard doesn’t dismiss on a [Return], and 2) [Return] is always detected.
I work around this by passing whatever action I would usually execute in .onSubmit to the UITextFieldDelegate:
class TextFieldDelegate: NSObject, UITextFieldDelegate {
var shouldReturn: (() -> Bool)?
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if let shouldReturn = shouldReturn {
return shouldReturn()
}
else {
return true
}
}
}
Then in our SwiftUI view:
import Introspect
struct TestView: View {
enum FocusedField {
case username, password
}
@State private var username: String = ""
@State private var password: String = ""
var usernameFieldDelegate = TextFieldDelegate()
var passwordFieldDelegate = TextFieldDelegate()
@FocusState private var focusedField: FocusedField?
var body: some View {
VStack {
TextField(text: $username)
.focused($focusedField, equals: .username)
.introspectTextField(customize: { textField in
usernameFieldDelegate.shouldReturn = {
if usernameIsValid() {
focusedField = .password
}
return false
}
textField.delegate = usernameFieldDelegate
})
SecureField(text: $password)
.focused($focusedField, equals: .password)
.introspectTextField(customize: { textField in
passwordFieldDelegate.shouldReturn = {
validateAndProceed()
return false
}
textField.delegate = passwordFieldDelegate
})
}
}
func usernameIsValid() -> Bool {
return true
}
func validateAndProceed() {}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With