Here is a pure swiftUI version, based on Antoine Weber's answer to his question above and what I found in this blog and this gist. It incorporates
- a clear button,
- a cancel button,
- resigning keyboard on dragging in the list and
- hiding the navigation view when the search text field is selected.
Resigning the keyboard on drag in the list can be realized using a method on UIApplication window following these answers. For easier handling I created an extension on UIApplication and view modifier for this extension and finally an extension to View:
// Deprecated with iOS 15
//extension UIApplication {
// func endEditing(_ force: Bool) {
// self.windows
// .filter{$0.isKeyWindow}
// .first?
// .endEditing(force)
// }
//}
// Update for iOS 15
// MARK: - UIApplication extension for resgning keyboard on pressing the cancel buttion of the search bar
extension UIApplication {
/// Resigns the keyboard.
///
/// Used for resigning the keyboard when pressing the cancel button in a searchbar based on [this](https://stackoverflow.com/a/58473985/3687284) solution.
/// - Parameter force: set true to resign the keyboard.
func endEditing(_ force: Bool) {
let scenes = UIApplication.shared.connectedScenes
let windowScene = scenes.first as? UIWindowScene
let window = windowScene?.windows.first
window?.endEditing(force)
}
}
struct ResignKeyboardOnDragGesture: ViewModifier {
var gesture = DragGesture().onChanged{_ in
UIApplication.shared.endEditing(true)
}
func body(content: Content) -> some View {
content.gesture(gesture)
}
}
extension View {
func resignKeyboardOnDragGesture() -> some View {
return modifier(ResignKeyboardOnDragGesture())
}
}
So the final modifier for resigning the keyboard is just one modifier that has to be placed on the list like this:
List {
ForEach(...) {
//...
}
}
.resignKeyboardOnDragGesture()
The complete swiftUI project code for the search bar with a sample list of names is as follows. You can paste it into ContentView.swift of a new swiftUI project and play with it.
import SwiftUI
struct ContentView: View {
let array = ["Peter", "Paul", "Mary", "Anna-Lena", "George", "John", "Greg", "Thomas", "Robert", "Bernie", "Mike", "Benno", "Hugo", "Miles", "Michael", "Mikel", "Tim", "Tom", "Lottie", "Lorrie", "Barbara"]
@State private var searchText = ""
@State private var showCancelButton: Bool = false
var body: some View {
NavigationView {
VStack {
// Search view
HStack {
HStack {
Image(systemName: "magnifyingglass")
TextField("search", text: $searchText, onEditingChanged: { isEditing in
self.showCancelButton = true
}, onCommit: {
print("onCommit")
}).foregroundColor(.primary)
Button(action: {
self.searchText = ""
}) {
Image(systemName: "xmark.circle.fill").opacity(searchText == "" ? 0 : 1)
}
}
.padding(EdgeInsets(top: 8, leading: 6, bottom: 8, trailing: 6))
.foregroundColor(.secondary)
.background(Color(.secondarySystemBackground))
.cornerRadius(10.0)
if showCancelButton {
Button("Cancel") {
UIApplication.shared.endEditing(true) // this must be placed before the other commands here
self.searchText = ""
self.showCancelButton = false
}
.foregroundColor(Color(.systemBlue))
}
}
.padding(.horizontal)
.navigationBarHidden(showCancelButton) // .animation(.default) // animation does not work properly
List {
// Filtered list of names
ForEach(array.filter{$0.hasPrefix(searchText) || searchText == ""}, id:\.self) {
searchText in Text(searchText)
}
}
.navigationBarTitle(Text("Search"))
.resignKeyboardOnDragGesture()
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
Group {
ContentView()
.environment(\.colorScheme, .light)
ContentView()
.environment(\.colorScheme, .dark)
}
}
}
// Deprecated with iOS 15
//extension UIApplication {
// func endEditing(_ force: Bool) {
// self.windows
// .filter{$0.isKeyWindow}
// .first?
// .endEditing(force)
// }
//}
// Update for iOS 15
// MARK: - UIApplication extension for resgning keyboard on pressing the cancel buttion of the search bar
extension UIApplication {
/// Resigns the keyboard.
///
/// Used for resigning the keyboard when pressing the cancel button in a searchbar based on [this](https://stackoverflow.com/a/58473985/3687284) solution.
/// - Parameter force: set true to resign the keyboard.
func endEditing(_ force: Bool) {
let scenes = UIApplication.shared.connectedScenes
let windowScene = scenes.first as? UIWindowScene
let window = windowScene?.windows.first
window?.endEditing(force)
}
}
struct ResignKeyboardOnDragGesture: ViewModifier {
var gesture = DragGesture().onChanged{_ in
UIApplication.shared.endEditing(true)
}
func body(content: Content) -> some View {
content.gesture(gesture)
}
}
extension View {
func resignKeyboardOnDragGesture() -> some View {
return modifier(ResignKeyboardOnDragGesture())
}
}
The final result for the search bar, when initially displayed looks like this

and when the search bar is edited like this:

In Action:
