searchable search bar loses input onSubmit or cancel

In the following example, every time a user submits or cancels the search, the entered text removed from the search bar. If a user wants to modify the serach expression (for example because of a typo) they have to enter the entire search expression again.

In our app users enter a query in a domain specific query language in the search bar. These queries can become complex and users often want to refine the query after surveying previous results. Right now, users have to enter their previous search again if they want to modify it.

How can I modify the example code in order to prevent the search expression to be removed from the search bar after submitting or canceling the search?

import SwiftUI

struct SomeView: View {
    
    @State private var searchExpression = ""
    @State private var isPresented = false
    
    init() {
        
    }
    
    var body: some View {
        NavigationStack {
            VStack {
                Text("some view")
                    .searchable(text: $searchExpression, isPresented: $isPresented)
                    .onSubmit(of: .search) {
                        // dismissing the search bar will clear the text the from searchExpression var. This also happens if the user cancels the search.
                        isPresented = false
                    }
            }
        }
    }
}

#Preview {
    SomeView()
}

Please note that I intentionally did not use the @Environment(\.dismissSearch) variable because it is not available in the SomeView. The described behavior is the same when using a more complex view hierarchy that uses the environment varaible to dismiss the search bar.

Accepted Reply

In case anyone is wondering how to fix this issue I describe what is happening and how I worked around it.

When a user is submitting their input from a searchable view the searchable view will display an empty input field. However, the value bound to the view still has the value, the user submitted, it is just not displayed. This seems to be a bug in SwiftUI. I worked around it by first setting the bound value to an empty String, waiting a short period of time and the setting the value to the previous user input again.

import SwiftUI

struct SomeView: View {
    
    @State private var searchExpression = ""
    @State private var temporarySearchExpression = "" // a new state to temporarily hold the search expression
    @State private var isPresented = false
    
    init() {
        
    }
    
    var body: some View {
        NavigationStack {
            VStack {
                Text("some view")
                    .searchable(text: $searchExpression, isPresented: $isPresented)
                    .onSubmit(of: .search) {
                        isPresented = false
                        temporarySearchExpression = searchExpression
                        searchExpression = ""

                        // Waiting for a short period of time is necessary. 
                        // It seems SwiftUI needs some time to process the state changes.
                        DispatchQueue.main.asyncAfter(deadline: .now().advanced(by: DispatchTimeInterval.milliseconds(1))) {
                            searchExpression = temporarySearchExpression
                        }
                    }
            }
        }
    }
}

#Preview {
    SomeView()
}

Replies

In case anyone is wondering how to fix this issue I describe what is happening and how I worked around it.

When a user is submitting their input from a searchable view the searchable view will display an empty input field. However, the value bound to the view still has the value, the user submitted, it is just not displayed. This seems to be a bug in SwiftUI. I worked around it by first setting the bound value to an empty String, waiting a short period of time and the setting the value to the previous user input again.

import SwiftUI

struct SomeView: View {
    
    @State private var searchExpression = ""
    @State private var temporarySearchExpression = "" // a new state to temporarily hold the search expression
    @State private var isPresented = false
    
    init() {
        
    }
    
    var body: some View {
        NavigationStack {
            VStack {
                Text("some view")
                    .searchable(text: $searchExpression, isPresented: $isPresented)
                    .onSubmit(of: .search) {
                        isPresented = false
                        temporarySearchExpression = searchExpression
                        searchExpression = ""

                        // Waiting for a short period of time is necessary. 
                        // It seems SwiftUI needs some time to process the state changes.
                        DispatchQueue.main.asyncAfter(deadline: .now().advanced(by: DispatchTimeInterval.milliseconds(1))) {
                            searchExpression = temporarySearchExpression
                        }
                    }
            }
        }
    }
}

#Preview {
    SomeView()
}