The searchable modifier (iOS 15+) adds a system search bar wherever the platform expects
one: in the master column on iOS and iPadOS, in the trailing toolbar on macOS. You provide a
text binding and react to it; suggestions, scopes, and tokens layer on top.
Wiring the Query
Attach searchable to the NavigationView or NavigationStack and drive requests from the
binding. The placement parameter (automatic, sidebar, toolbar,
navigationBarDrawer) is a suggestion SwiftUI may ignore.
NavigationView {
List(viewModel.repos) { repo in
RepoRow(repo: repo)
}
.searchable(text: $query, prompt: Text("Search"))
.onChange(of: query) { newQuery in
Task { await viewModel.search(matching: newQuery) }
}
}
searchable(text:isPresented:) adds programmatic control over whether the search field is
active.
Reacting to Search State
Two environment values work inside any view under a searchable modifier, and only there:
isSearching says whether the user is interacting with the search bar, and dismissSearch
ends the interaction. Typical use: overlay quick results while searching.
@Environment(\.isSearching) var isSearching
@Environment(\.dismissSearch) var dismissSearch
.overlay {
if isSearching && !query.isEmpty {
SearchResultView(query: query)
}
}
Suggestions, Scopes, Tokens
searchSuggestions shows a suggestion list; searchCompletion turns a row into a button
that fills the query. Apply searchCompletion only to non-interactive views such as Text
or Label, because it wraps its content in a Button.
.searchSuggestions {
ForEach(suggestions, id: \.self) { suggestion in
Text(suggestion)
.searchCompletion(suggestion)
}
}
searchScopes($scope) { ... } renders a segmented control under the bar (iOS 16+). Token
fields come from the searchable(text:tokens:suggestedTokens:) overload: tapping a
suggested token moves it into the bar and into the selected-tokens binding, and you can also
append tokens yourself by parsing the query in onChange.