swiftui

How to display a search bar with SwiftUI


The new SwiftUI framework does not seem to provide a built-in search bar component. Should I use a UISearchController and wrap it in some way, or should I use a simple textfield and update the data according to the textfield input?

2019 EDIT: A current workaround is to use a TextField as a searchBar but it does not have the search icon.

enter image description here


Solution

  • 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

    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

    enter image description here

    and when the search bar is edited like this:

    enter image description here

    In Action:

    enter image description here