SwiftUI dismiss keyboard when tapping segmentedControl

4.5k Views Asked by At

I have a TextField in SwiftUI that needs to use a different keyboard depending on the value of a @State variable determined by a SegementedControl() picker.

How can I dismiss the keyboard (like send an endEditing event) when the user taps a different segment? I need to do this because I want to change the keyboard type and if the textField is the responder, the keyboard won't change.

I have this extension:

extension UIApplication {
    func endEditing() {
        sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
    }
}

And I can do something like

UIApplication.shared.endEditing()

But I don't know where or how to call this when the user taps a different segment.

I have tried putting a tapGesture on the Picker and the keyboard does dismiss, but the tap does not pass through to the picker so it does not change.

Code snippet here:

@State private var type:String = "name"

. . .

Form {
    Section(header: Text("Search Type")) {
        Picker("", selection: $type) {
            Text("By Name").tag("name")
            Text("By AppId").tag("id")
        }.pickerStyle(SegmentedPickerStyle())
    }

    Section(header: Text("Enter search value")) {
        TextField(self.searchPlaceHolder, text: $searchValue)
            .keyboardType(self.type == "name" ? UIKeyboardType.alphabet : UIKeyboardType.numberPad)
    }
}
4

There are 4 best solutions below

1
RPatel99 On BEST ANSWER

Attach a custom Binding to the Picker that calls endEditing() whenever it is set:

Section(header: Text("Search Type")) {
    Picker("", selection: Binding(get: {
        self.type
    }, set: { (res) in
        self.type = res
        UIApplication.shared.endEditing()
    })) {
        Text("By Name").tag("name")
        Text("By AppId").tag("id")
    }.pickerStyle(SegmentedPickerStyle())
}
0
lawrencebensaid On

Update since iOS 13 / iPadOS 13 was released.

Since there is now support for multiple windows in one app you need to loop through the UIWindows and end editing one-by-one.

UIApplication.shared.windows.forEach { $0.endEditing(false) }
0
Asperi On

SwiftUI 2.0

Now it can be done in more elegant way, with .onChange (actually it can be attached to any view, but at TextField looks appropriate, by intention)

TextField("Placeholder", text: $searchValue)
    .keyboardType(self.type == "name" ? UIKeyboardType.alphabet : UIKeyboardType.numberPad)
    .onChange(of: type) { _ in
        UIApplication.shared.endEditing()   // << here !!
    }

SwiftUI 1.0+

There are much similar to above approaches

a) requires import Combine...

TextField("Placeholder", text: $searchValue)
    .keyboardType(self.type == "name" ? UIKeyboardType.alphabet : UIKeyboardType.numberPad)
    .onChange(of: type) { _ in
        UIApplication.shared.endEditing()
    }
    .onReceive(Just(type)) { _ in
        UIApplication.shared.endEditing()   // << here !!
    }

b) ... and not

TextField("Placeholder", text: $searchValue)
    .keyboardType(self.type == "name" ? UIKeyboardType.alphabet : UIKeyboardType.numberPad)
    .onReceive([type].publisher) { _ in
        UIApplication.shared.endEditing()   // << here !!
    }
1
Youngerman On

For SwiftUI 3 use FocusState and .focused(…)