JohnSF
JohnSF

Reputation: 4300

SwiftUI .searchable Environment isSearching Not Behaving as Expected

I'm trying to understand how to know when a user has tapped into the search field in an iOS app that is using the .searchable modifier. The Apple docs say one can get the Environment value to find out when the user interacts with a search field that’s produced by one of the searchable modifiers, like searchable(text:placement:prompt:)

However this first code does not work. The Text always says Not Searching. I must be missing something fundamental:

struct ContentView: View {
    @Environment(\.isSearching) var isSearching
    @State private var searchText: String = ""

    var items = ["One", "Two", "Three", "Four", "Five"]

    var body: some View {
        NavigationStack {
            VStack {
                Text(isSearching ? "Searching" : "Not Searching")
                List {
                    ForEach(searchResults, id: \.self) { item in
                        Text(item)
                    }
                }
                .listStyle(PlainListStyle())
                .searchable(text: $searchText)
            }//v
            .padding()
        }//nav
    }//body

    private var searchResults: [String] {
        if searchText.isEmpty {
            return items
        } else {
            return items.filter({ item in
                item.contains(searchText)
            })
        }
    }//var search results

}//struct

But, if I extract the bulk of the code to another view, this does work:

struct ContentView2: View {
    @State private var searchText: String = ""

    var body: some View {
        NavigationStack {
            SearchedView2 (searchText: $searchText)
                .searchable(text: $searchText)
        }//nav
    }//body

}//struct

struct SearchedView2: View {
    @Environment(\.isSearching) var isSearching

    @Binding var searchText: String
    var items = ["One", "Two", "Three", "Four", "Five"]

    var body: some View {
        VStack {
            Text(isSearching ? "Searching" : "Not Searching")
            List {
                ForEach(searchResults, id: \.self) { item in
                    Text(item)
                }
            }
            .listStyle(PlainListStyle())
        }//v
        .padding()
    }//body

    private var searchResults: [String] {
        if searchText.isEmpty {
            return items
        } else {
            return items.filter({ item in
                item.contains(searchText)
            })
        }
    }//var search results
}//struct

I'm confused. Why doesn't the first code work? Any guidance would be appreciated. Xcode 14.0.1 iOS 16.0

Upvotes: 2

Views: 1786

Answers (1)

Isaac
Isaac

Reputation: 498

Let's give a look at the documentation. The @Environment() wrapper is initialized with a given environment value \.isSearching, which represents the context within which it was called - SearchingExampl(), then it tries to find the search field created by the .searchable() modifier and provide all its functionality, including the isSearching observer.

struct SearchingExample: View {
    @State private var text = ""

    var body: some View {
        NavigationView {
            SearchedView()
                .searchable(text: $text)
        }
    }
}

struct SearchedView: View {
    @Environment(\.isSearching) private var isSearching

    var body: some View {
        Text(isSearching ? "Searching!" : "Not searching.")
    }
}

Theory:

https://developer.apple.com/documentation/swiftui/environment?language=swift

https://developer.apple.com/documentation/swiftui/environmentvalues

https://developer.apple.com/documentation/swiftui/environmentvalues/issearching

Practice:

https://developer.apple.com/documentation/swiftui/adding-search-to-your-app

Upvotes: 3

Related Questions