axiom-swiftui-search-ref
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSwiftUI Search API Reference
SwiftUI 搜索API参考
Overview
概述
SwiftUI search is environment-based and navigation-consumed. You attach to a view, but a navigation container (NavigationStack, NavigationSplitView, or TabView) renders the actual search field. This indirection is the source of most search bugs.
.searchable()SwiftUI搜索是基于环境且由导航容器托管的。你可以将附加到任意视图,但实际的搜索栏是由导航容器(NavigationStack、NavigationSplitView或TabView)渲染的。这种间接性是大多数搜索相关bug的根源。
.searchable()API Evolution
API演进
| iOS | Key Additions |
|---|---|
| 15 | |
| 16 | Search scopes ( |
| 16.4 | Search scope |
| 17 | |
| 17.1 | |
| 18 | |
| 26 | Bottom-aligned search, |
| iOS版本 | 关键新增功能 |
|---|---|
| 15 | |
| 16 | 搜索范围( |
| 16.4 | 搜索范围 |
| 17 | |
| 17.1 | |
| 18 | |
| 26 | 底部对齐搜索、 |
When to Use This Skill
何时使用本技能
- Adding search to a SwiftUI list or collection
- Implementing filter-as-you-type or submit-based search
- Adding search suggestions with auto-completion
- Using search scopes to narrow results by category
- Using search tokens for structured queries
- Controlling search focus programmatically
- Debugging "search field doesn't appear" issues
For iOS 26 search features (bottom-aligned, minimized toolbar, search tab role), see .
axiom-swiftui-26-ref- 为SwiftUI列表或集合添加搜索功能
- 实现输入即过滤或提交式搜索
- 添加带自动补全的搜索建议
- 使用搜索范围按类别缩小结果
- 使用搜索令牌构建结构化查询
- 程序化控制搜索焦点
- 排查“搜索栏不显示”等问题
关于iOS 26的搜索功能(底部对齐、最小化工具栏、搜索标签页角色),请查看。
axiom-swiftui-26-refPart 1: The searchable Modifier
第一部分:.searchable修饰符
Core API
核心API
swift
.searchable(
text: Binding<String>,
placement: SearchFieldPlacement = .automatic,
prompt: LocalizedStringKey
)Availability: iOS 15+, macOS 12+, tvOS 15+, watchOS 8+
swift
.searchable(
text: Binding<String>,
placement: SearchFieldPlacement = .automatic,
prompt: LocalizedStringKey
)兼容性:iOS 15+、macOS 12+、tvOS 15+、watchOS 8+
How It Works
工作原理
- You attach to a view
.searchable(text: $query) - The nearest navigation container (NavigationStack, NavigationSplitView) renders the search field
- The view receives and
isSearchingthrough the environmentdismissSearch - Your view filters or queries based on the bound text
swift
struct RecipeListView: View {
@State private var searchText = ""
let recipes: [Recipe]
var body: some View {
NavigationStack {
List(filteredRecipes) { recipe in
NavigationLink(recipe.name, value: recipe)
}
.navigationTitle("Recipes")
.searchable(text: $searchText, prompt: "Find a recipe")
}
}
var filteredRecipes: [Recipe] {
if searchText.isEmpty { return recipes }
return recipes.filter { $0.name.localizedCaseInsensitiveContains(searchText) }
}
}- 将附加到一个视图
.searchable(text: $query) - 最近的导航容器(NavigationStack、NavigationSplitView)渲染搜索栏
- 视图通过环境获取和
isSearchingdismissSearch - 视图根据绑定的文本进行过滤或查询
swift
struct RecipeListView: View {
@State private var searchText = ""
let recipes: [Recipe]
var body: some View {
NavigationStack {
List(filteredRecipes) { recipe in
NavigationLink(recipe.name, value: recipe)
}
.navigationTitle("Recipes")
.searchable(text: $searchText, prompt: "Find a recipe")
}
}
var filteredRecipes: [Recipe] {
if searchText.isEmpty { return recipes }
return recipes.filter { $0.name.localizedCaseInsensitiveContains(searchText) }
}
}Placement Options
位置选项
| Placement | Behavior |
|---|---|
| System decides (recommended) |
| Below navigation bar title (iOS) |
| Always visible, not hidden on scroll |
| In the sidebar column (NavigationSplitView) |
| In the toolbar area |
| In toolbar's principal section |
Gotcha: SwiftUI may ignore your placement preference if the view hierarchy doesn't support it. Always test on the target platform.
| 位置 | 行为 |
|---|---|
| 由系统决定(推荐) |
| 位于导航栏标题下方(iOS) |
| 始终可见,滚动时不会隐藏 |
| 位于侧边栏列中(NavigationSplitView) |
| 位于工具栏区域 |
| 位于工具栏的主区域 |
注意:如果视图层级不支持,SwiftUI可能会忽略你设置的位置偏好。请务必在目标平台上测试。
Column Association in NavigationSplitView
NavigationSplitView中的列关联
Where you attach determines which column displays the search field:
.searchableswift
NavigationSplitView {
SidebarView()
.searchable(text: $query) // Search in sidebar
} detail: {
DetailView()
}
// vs.
NavigationSplitView {
SidebarView()
} detail: {
DetailView()
.searchable(text: $query) // Search in detail
}
// vs.
NavigationSplitView {
SidebarView()
} detail: {
DetailView()
}
.searchable(text: $query) // System decides column.searchableswift
NavigationSplitView {
SidebarView()
.searchable(text: $query) // 搜索栏在侧边栏
} detail: {
DetailView()
}
// 对比
NavigationSplitView {
SidebarView()
} detail: {
DetailView()
.searchable(text: $query) // 搜索栏在详情页
}
// 对比
NavigationSplitView {
SidebarView()
} detail: {
DetailView()
}
.searchable(text: $query) // 由系统决定显示列Part 2: Displaying Search Results
第二部分:显示搜索结果
isSearching Environment
isSearching环境变量
swift
@Environment(\.isSearching) private var isSearchingAvailability: iOS 15+
Becomes when the user activates search (taps the field), when they cancel or you call .
truefalsedismissSearchCritical rule: must be read from a child of the view that has . SwiftUI sets the value in the searchable view's environment and does not propagate it upward.
isSearching.searchableswift
// Pattern: Overlay search results when searching
struct WeatherCityList: View {
@State private var searchText = ""
var body: some View {
NavigationStack {
// SearchResultsOverlay reads isSearching
SearchResultsOverlay(searchText: searchText) {
List(favoriteCities) { city in
CityRow(city: city)
}
}
.searchable(text: $searchText)
.navigationTitle("Weather")
}
}
}
struct SearchResultsOverlay<Content: View>: View {
let searchText: String
@ViewBuilder let content: Content
@Environment(\.isSearching) private var isSearching
var body: some View {
if isSearching {
// Show search results
SearchResults(query: searchText)
} else {
content
}
}
}swift
@Environment(\.isSearching) private var isSearching兼容性:iOS 15+
当用户激活搜索(点击搜索栏)时变为,当用户取消或调用时变为。
truedismissSearchfalse关键规则:必须从附加了.searchable的视图的子视图中读取。SwiftUI会在附加了.searchable的视图的环境中设置该值,不会向上传播。
isSearchingswift
// 模式:搜索时覆盖显示搜索结果
struct WeatherCityList: View {
@State private var searchText = ""
var body: some View {
NavigationStack {
// SearchResultsOverlay读取isSearching
SearchResultsOverlay(searchText: searchText) {
List(favoriteCities) { city in
CityRow(city: city)
}
}
.searchable(text: $searchText)
.navigationTitle("Weather")
}
}
}
struct SearchResultsOverlay<Content: View>: View {
let searchText: String
@ViewBuilder let content: Content
@Environment(\.isSearching) private var isSearching
var body: some View {
if isSearching {
// 显示搜索结果
SearchResults(query: searchText)
} else {
content
}
}
}dismissSearch Environment
dismissSearch环境变量
swift
@Environment(\.dismissSearch) private var dismissSearchAvailability: iOS 15+
Calling clears the search text, removes focus, and sets to . Must be called from inside the searchable view hierarchy.
dismissSearch()isSearchingfalseswift
struct SearchResults: View {
@Environment(\.dismissSearch) private var dismissSearch
var body: some View {
List(results) { result in
Button(result.name) {
selectResult(result)
dismissSearch() // Close search after selection
}
}
}
}swift
@Environment(\.dismissSearch) private var dismissSearch兼容性:iOS 15+
调用会清空搜索文本、移除焦点并将设置为。必须在附加了.searchable的视图层级内调用。
dismissSearch()isSearchingfalseswift
struct SearchResults: View {
@Environment(\.dismissSearch) private var dismissSearch
var body: some View {
List(results) { result in
Button(result.name) {
selectResult(result)
dismissSearch() // 选择结果后关闭搜索
}
}
}
}Part 3: Search Suggestions
第三部分:搜索建议
Adding Suggestions
添加搜索建议
Pass a closure to :
suggestions.searchableswift
.searchable(text: $searchText) {
ForEach(suggestedResults) { suggestion in
Text(suggestion.name)
.searchCompletion(suggestion.name)
}
}Availability: iOS 15+
Suggestions appear in a list below the search field when the user is typing.
向传递闭包:
.searchablesuggestionsswift
.searchable(text: $searchText) {
ForEach(suggestedResults) { suggestion in
Text(suggestion.name)
.searchCompletion(suggestion.name)
}
}兼容性:iOS 15+
当用户输入时,建议会显示在搜索栏下方的列表中。
searchCompletion Modifier
searchCompletion修饰符
.searchCompletion(_:)swift
.searchable(text: $searchText) {
ForEach(matchingColors) { color in
HStack {
Circle()
.fill(color.value)
.frame(width: 16, height: 16)
Text(color.name)
}
.searchCompletion(color.name) // Tapping fills search with color name
}
}Without : Suggestions display but tapping them does nothing to the search field. This is the most common suggestions bug.
.searchCompletion().searchCompletion(_:)swift
.searchable(text: $searchText) {
ForEach(matchingColors) { color in
HStack {
Circle()
.fill(color.value)
.frame(width: 16, height: 16)
Text(color.name)
}
.searchCompletion(color.name) // 点击后将颜色名称填入搜索栏
}
}如果没有:建议会显示,但点击后不会对搜索栏产生任何影响。这是搜索建议最常见的bug。
.searchCompletion()Complete Suggestion Pattern
完整的搜索建议模式
swift
struct ColorSearchView: View {
@State private var searchText = ""
let allColors: [NamedColor]
var body: some View {
NavigationStack {
List(filteredColors) { color in
ColorRow(color: color)
}
.navigationTitle("Colors")
.searchable(text: $searchText, prompt: "Search colors") {
ForEach(suggestedColors) { color in
Label(color.name, systemImage: "paintpalette")
.searchCompletion(color.name)
}
}
}
}
var suggestedColors: [NamedColor] {
guard !searchText.isEmpty else { return [] }
return allColors.filter {
$0.name.localizedCaseInsensitiveContains(searchText)
}
.prefix(5)
.map { $0 } // Convert ArraySlice to Array
}
var filteredColors: [NamedColor] {
if searchText.isEmpty { return allColors }
return allColors.filter {
$0.name.localizedCaseInsensitiveContains(searchText)
}
}
}swift
struct ColorSearchView: View {
@State private var searchText = ""
let allColors: [NamedColor]
var body: some View {
NavigationStack {
List(filteredColors) { color in
ColorRow(color: color)
}
.navigationTitle("Colors")
.searchable(text: $searchText, prompt: "Search colors") {
ForEach(suggestedColors) { color in
Label(color.name, systemImage: "paintpalette")
.searchCompletion(color.name)
}
}
}
}
var suggestedColors: [NamedColor] {
guard !searchText.isEmpty else { return [] }
return allColors.filter {
$0.name.localizedCaseInsensitiveContains(searchText)
}
.prefix(5)
.map { $0 } // 将ArraySlice转换为Array
}
var filteredColors: [NamedColor] {
if searchText.isEmpty { return allColors }
return allColors.filter {
$0.name.localizedCaseInsensitiveContains(searchText)
}
}
}Part 4: Search Submission
第四部分:搜索提交
onSubmit(of: .search)
onSubmit(of: .search)
Triggers when the user presses Return/Enter in the search field:
swift
.searchable(text: $searchText)
.onSubmit(of: .search) {
performSearch(searchText)
}Availability: iOS 15+
当用户在搜索栏按下Return/Enter键时触发:
swift
.searchable(text: $searchText)
.onSubmit(of: .search) {
performSearch(searchText)
}兼容性:iOS 15+
Filter vs Submit Decision
过滤 vs 提交的决策
| Pattern | Use When | Example |
|---|---|---|
| Filter-as-you-type | Local data, fast filtering | Contacts, settings |
| Submit-based search | Network requests, expensive queries | App Store, web search |
| Combined | Suggestions filter locally, submit triggers server | Maps, shopping |
| 模式 | 使用场景 | 示例 |
|---|---|---|
| 输入即过滤 | 本地数据、快速过滤 | 联系人、设置 |
| 提交式搜索 | 网络请求、耗时查询 | App Store、网页搜索 |
| 组合模式 | 本地建议过滤,提交触发服务器查询 | 地图、购物 |
Combined Suggestions + Submit Pattern
搜索建议+提交的组合模式
swift
struct StoreSearchView: View {
@State private var searchText = ""
@State private var searchResults: [Product] = []
let recentSearches: [String]
var body: some View {
NavigationStack {
List(searchResults) { product in
ProductRow(product: product)
}
.navigationTitle("Store")
.searchable(text: $searchText, prompt: "Search products") {
// Local suggestions from recent searches
ForEach(matchingRecent, id: \.self) { term in
Label(term, systemImage: "clock")
.searchCompletion(term)
}
}
.onSubmit(of: .search) {
// Server search on submit
Task {
searchResults = await ProductAPI.search(searchText)
}
}
}
}
var matchingRecent: [String] {
guard !searchText.isEmpty else { return recentSearches }
return recentSearches.filter {
$0.localizedCaseInsensitiveContains(searchText)
}
}
}swift
struct StoreSearchView: View {
@State private var searchText = ""
@State private var searchResults: [Product] = []
let recentSearches: [String]
var body: some View {
NavigationStack {
List(searchResults) { product in
ProductRow(product: product)
}
.navigationTitle("Store")
.searchable(text: $searchText, prompt: "Search products") {
// 来自最近搜索的本地建议
ForEach(matchingRecent, id: \.self) { term in
Label(term, systemImage: "clock")
.searchCompletion(term)
}
}
.onSubmit(of: .search) {
// 提交时触发服务器搜索
Task {
searchResults = await ProductAPI.search(searchText)
}
}
}
}
var matchingRecent: [String] {
guard !searchText.isEmpty else { return recentSearches }
return recentSearches.filter {
$0.localizedCaseInsensitiveContains(searchText)
}
}
}Part 5: Search Scopes (iOS 16+)
第五部分:搜索范围(iOS 16+)
Adding Scopes
添加搜索范围
Scopes add a segmented picker below the search field for narrowing results by category:
swift
enum SearchScope: String, CaseIterable {
case all = "All"
case recipes = "Recipes"
case ingredients = "Ingredients"
}
struct ScopedSearchView: View {
@State private var searchText = ""
@State private var searchScope: SearchScope = .all
var body: some View {
NavigationStack {
List(filteredResults) { result in
ResultRow(result: result)
}
.navigationTitle("Cookbook")
.searchable(text: $searchText)
.searchScopes($searchScope) {
ForEach(SearchScope.allCases, id: \.self) { scope in
Text(scope.rawValue).tag(scope)
}
}
}
}
}Availability: iOS 16+, macOS 13+
搜索范围会在搜索栏下方添加分段选择器,用于按类别缩小结果范围:
swift
enum SearchScope: String, CaseIterable {
case all = "All"
case recipes = "Recipes"
case ingredients = "Ingredients"
}
struct ScopedSearchView: View {
@State private var searchText = ""
@State private var searchScope: SearchScope = .all
var body: some View {
NavigationStack {
List(filteredResults) { result in
ResultRow(result: result)
}
.navigationTitle("Cookbook")
.searchable(text: $searchText)
.searchScopes($searchScope) {
ForEach(SearchScope.allCases, id: \.self) { scope in
Text(scope.rawValue).tag(scope)
}
}
}
}
}兼容性:iOS 16+、macOS 13+
Scope Activation (iOS 16.4+)
范围激活时机(iOS 16.4+)
Control when scopes appear:
swift
.searchScopes($searchScope, activation: .onTextEntry) {
// Scopes appear only when user starts typing
ForEach(SearchScope.allCases, id: \.self) { scope in
Text(scope.rawValue).tag(scope)
}
}| Activation | Behavior |
|---|---|
| System default |
| Scopes appear when user types text |
| Scopes appear when search is activated |
Platform differences:
- iOS/iPadOS: Scopes appear on text entry by default, dismiss on cancel
- macOS: Scopes appear when search is presented, dismiss on cancel
控制搜索范围的显示时机:
swift
.searchScopes($searchScope, activation: .onTextEntry) {
// 仅当用户开始输入时显示范围
ForEach(SearchScope.allCases, id: \.self) { scope in
Text(scope.rawValue).tag(scope)
}
}| 激活时机 | 行为 |
|---|---|
| 系统默认 |
| 用户输入文本时显示范围 |
| 激活搜索时显示范围 |
平台差异:
- iOS/iPadOS:默认在输入文本时显示范围,取消搜索时隐藏
- macOS:激活搜索时显示范围,取消搜索时隐藏
Part 6: Search Tokens (iOS 16+)
第六部分:搜索令牌(iOS 16+)
Tokens are structured search elements that appear as "pills" in the search field alongside free text.
搜索令牌是结构化的搜索元素,在搜索栏中以“胶囊”形式与自由文本共存。
Basic Tokens
基础令牌
swift
enum RecipeToken: Identifiable, Hashable {
case cuisine(String)
case difficulty(String)
var id: Self { self }
}
struct TokenSearchView: View {
@State private var searchText = ""
@State private var tokens: [RecipeToken] = []
var body: some View {
NavigationStack {
List(filteredRecipes) { recipe in
RecipeRow(recipe: recipe)
}
.navigationTitle("Recipes")
.searchable(text: $searchText, tokens: $tokens) { token in
switch token {
case .cuisine(let name):
Label(name, systemImage: "globe")
case .difficulty(let name):
Label(name, systemImage: "star")
}
}
}
}
}Availability: iOS 16+
Token model requirements: Each token element must conform to .
Identifiableswift
enum RecipeToken: Identifiable, Hashable {
case cuisine(String)
case difficulty(String)
var id: Self { self }
}
struct TokenSearchView: View {
@State private var searchText = ""
@State private var tokens: [RecipeToken] = []
var body: some View {
NavigationStack {
List(filteredRecipes) { recipe in
RecipeRow(recipe: recipe)
}
.navigationTitle("Recipes")
.searchable(text: $searchText, tokens: $tokens) { token in
switch token {
case .cuisine(let name):
Label(name, systemImage: "globe")
case .difficulty(let name):
Label(name, systemImage: "star")
}
}
}
}
}兼容性:iOS 16+
令牌模型要求:每个令牌元素必须遵循协议。
IdentifiableSuggested Tokens (iOS 17+)
建议令牌(iOS 17+)
swift
.searchable(
text: $searchText,
tokens: $tokens,
suggestedTokens: $suggestedTokens,
prompt: "Search recipes"
) { token in
Label(token.displayName, systemImage: token.icon)
}Availability: iOS 17+ adds and parameters.
suggestedTokensisPresentedswift
.searchable(
text: $searchText,
tokens: $tokens,
suggestedTokens: $suggestedTokens,
prompt: "Search recipes"
) { token in
Label(token.displayName, systemImage: token.icon)
}兼容性:iOS 17+新增了和参数。
suggestedTokensisPresentedCombined Tokens + Text Filtering
令牌+文本过滤的组合
swift
var filteredRecipes: [Recipe] {
var results = allRecipes
// Apply token filters
for token in tokens {
switch token {
case .cuisine(let cuisine):
results = results.filter { $0.cuisine == cuisine }
case .difficulty(let difficulty):
results = results.filter { $0.difficulty == difficulty }
}
}
// Apply text filter
if !searchText.isEmpty {
results = results.filter {
$0.name.localizedCaseInsensitiveContains(searchText)
}
}
return results
}swift
var filteredRecipes: [Recipe] {
var results = allRecipes
// 应用令牌过滤
for token in tokens {
switch token {
case .cuisine(let cuisine):
results = results.filter { $0.cuisine == cuisine }
case .difficulty(let difficulty):
results = results.filter { $0.difficulty == difficulty }
}
}
// 应用文本过滤
if !searchText.isEmpty {
results = results.filter {
$0.name.localizedCaseInsensitiveContains(searchText)
}
}
return results
}Part 7: Programmatic Search Control (iOS 18+)
第七部分:程序化搜索控制(iOS 18+)
searchFocused
searchFocused
Bind a to the search field to activate or dismiss search programmatically:
FocusState<Bool>swift
struct ProgrammaticSearchView: View {
@State private var searchText = ""
@FocusState private var isSearchFocused: Bool
var body: some View {
NavigationStack {
VStack {
Button("Start Search") {
isSearchFocused = true // Activate search field
}
List(filteredItems) { item in
Text(item.name)
}
}
.navigationTitle("Items")
.searchable(text: $searchText)
.searchFocused($isSearchFocused)
}
}
}Availability: iOS 18+, macOS 15+, visionOS 2+
Note: For a non-boolean variant, use to match specific focus values.
.searchFocused(_:equals:)将与搜索栏绑定,以程序化方式激活或关闭搜索:
FocusState<Bool>swift
struct ProgrammaticSearchView: View {
@State private var searchText = ""
@FocusState private var isSearchFocused: Bool
var body: some View {
NavigationStack {
VStack {
Button("Start Search") {
isSearchFocused = true // 激活搜索栏
}
List(filteredItems) { item in
Text(item.name)
}
}
.navigationTitle("Items")
.searchable(text: $searchText)
.searchFocused($isSearchFocused)
}
}
}兼容性:iOS 18+、macOS 15+、visionOS 2+
注意:如需非布尔值变体,可使用匹配特定焦点值。
.searchFocused(_:equals:)Comparison with dismissSearch
与dismissSearch的对比
| API | Direction | iOS |
|---|---|---|
| Dismiss only | 15+ |
| Activate or dismiss | 18+ |
Use if you only need to close search. Use when you need to programmatically open search (e.g., a floating action button that opens search).
dismissSearchsearchFocused| API | 功能方向 | iOS版本 |
|---|---|---|
| 仅支持关闭 | 15+ |
| 支持激活或关闭 | 18+ |
如果仅需要关闭搜索,使用。当需要程序化打开搜索时(例如,点击浮动操作按钮打开搜索),使用。
dismissSearchsearchFocusedPart 8: Platform Behavior
第八部分:平台行为
SwiftUI search adapts automatically per platform:
| Platform | Default Behavior |
|---|---|
| iOS | Search bar in navigation bar. Scrolls out of view by default; pull down to reveal. |
| iPadOS | Same as iOS in compact; may appear in toolbar in regular width. |
| macOS | Trailing toolbar search field. Always visible. |
| watchOS | Dictation-first input. Search bar at top of list. |
| tvOS | Tab-based search with on-screen keyboard. |
SwiftUI搜索会根据平台自动适配:
| 平台 | 默认行为 |
|---|---|
| iOS | 搜索栏位于导航栏中。默认会随滚动隐藏;下拉可重新显示。 |
| iPadOS | 紧凑布局下与iOS相同;常规宽度下可能显示在工具栏中。 |
| macOS | 工具栏右侧的搜索栏。始终可见。 |
| watchOS | 以语音输入优先。搜索栏位于列表顶部。 |
| tvOS | 基于标签页的搜索,搭配屏幕键盘。 |
iOS-Specific Behavior
iOS特定行为
swift
// Always-visible search field (doesn't scroll away)
.searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always))
// Default: search field scrolls out, pull down to reveal
.searchable(text: $searchText)swift
// 始终可见的搜索栏(不会随滚动隐藏)
.searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always))
// 默认:搜索栏随滚动隐藏,下拉可显示
.searchable(text: $searchText)macOS-Specific Behavior
macOS特定行为
swift
// Search in toolbar (default on macOS)
.searchable(text: $searchText, placement: .toolbar)
// Search in sidebar
.searchable(text: $searchText, placement: .sidebar)swift
// 工具栏中的搜索栏(macOS默认)
.searchable(text: $searchText, placement: .toolbar)
// 侧边栏中的搜索栏
.searchable(text: $searchText, placement: .sidebar)Part 9: Common Gotchas
第九部分:常见问题
1. Search Field Doesn't Appear
1. 搜索栏不显示
Cause: is not inside a navigation container.
.searchableswift
// WRONG: No navigation container
List { ... }
.searchable(text: $query)
// CORRECT: Inside NavigationStack
NavigationStack {
List { ... }
.searchable(text: $query)
}原因:未放置在导航容器内。
.searchableswift
// 错误:没有导航容器
List { ... }
.searchable(text: $query)
// 正确:在NavigationStack内
NavigationStack {
List { ... }
.searchable(text: $query)
}2. isSearching Always Returns false
2. isSearching始终返回false
Cause: Reading from the wrong view level.
isSearchingswift
// WRONG: Reading from parent of searchable view
struct ParentView: View {
@Environment(\.isSearching) var isSearching // Always false
@State private var query = ""
var body: some View {
NavigationStack {
ChildView(isSearching: isSearching)
.searchable(text: $query)
}
}
}
// CORRECT: Reading from child view
struct ChildView: View {
@Environment(\.isSearching) var isSearching // Works
var body: some View {
if isSearching {
SearchResults()
} else {
DefaultContent()
}
}
}原因:从错误的视图层级读取。
isSearchingswift
// 错误:从附加了.searchable的视图的父视图读取
struct ParentView: View {
@Environment(\.isSearching) var isSearching // 始终为false
@State private var query = ""
var body: some View {
NavigationStack {
ChildView(isSearching: isSearching)
.searchable(text: $query)
}
}
}
// 正确:从子视图读取
struct ChildView: View {
@Environment(\.isSearching) var isSearching // 正常工作
var body: some View {
if isSearching {
SearchResults()
} else {
DefaultContent()
}
}
}3. Suggestions Don't Fill Search Field
3. 点击建议不会填入搜索栏
Cause: Missing on suggestion views.
.searchCompletion()swift
// WRONG: No searchCompletion
.searchable(text: $query) {
ForEach(suggestions) { s in
Text(s.name) // Displays but tapping does nothing
}
}
// CORRECT: With searchCompletion
.searchable(text: $query) {
ForEach(suggestions) { s in
Text(s.name)
.searchCompletion(s.name) // Fills search field on tap
}
}原因:建议视图缺少修饰符。
.searchCompletion()swift
// 错误:没有searchCompletion
.searchable(text: $query) {
ForEach(suggestions) { s in
Text(s.name) // 显示但点击无效
}
}
// 正确:添加searchCompletion
.searchable(text: $query) {
ForEach(suggestions) { s in
Text(s.name)
.searchCompletion(s.name) // 点击后填入搜索栏
}
}4. Placement on Wrong Navigation Level
4. 位置设置在错误的导航层级
Cause: Attaching to the wrong column in NavigationSplitView.
.searchableswift
// Might not appear where expected
NavigationSplitView {
SidebarView()
} detail: {
DetailView()
}
.searchable(text: $query) // System chooses column
// Explicit placement
NavigationSplitView {
SidebarView()
.searchable(text: $query, placement: .sidebar) // In sidebar
} detail: {
DetailView()
}原因:在NavigationSplitView中,.searchable附加到了错误的列。
swift
// 可能不会显示在预期位置
NavigationSplitView {
SidebarView()
} detail: {
DetailView()
}
.searchable(text: $query) // 由系统选择列
// 明确指定位置
NavigationSplitView {
SidebarView()
.searchable(text: $query, placement: .sidebar) // 在侧边栏
} detail: {
DetailView()
}5. Search Scopes Don't Appear
5. 搜索范围不显示
Cause: Scopes require on the same view. They also require a navigation container.
.searchableswift
// WRONG: Scopes without searchable
List { ... }
.searchScopes($scope) { ... }
// CORRECT: Scopes alongside searchable
List { ... }
.searchable(text: $query)
.searchScopes($scope) {
Text("All").tag(Scope.all)
Text("Recent").tag(Scope.recent)
}原因:搜索范围需要与附加到同一个视图,且需要导航容器。
.searchableswift
// 错误:没有searchable的情况下添加scopes
List { ... }
.searchScopes($scope) { ... }
// 正确:scopes与searchable一起使用
List { ... }
.searchable(text: $query)
.searchScopes($scope) {
Text("All").tag(Scope.all)
Text("Recent").tag(Scope.recent)
}6. iOS 26 Refinements
6. iOS 26优化功能
For bottom-aligned search, , , and , see . These build on the foundational APIs documented here.
.searchToolbarBehavior(.minimize)Tab(role: .search)DefaultToolbarItem(kind: .search)axiom-swiftui-26-ref关于底部对齐搜索、、和,请查看。这些功能基于本文档中的基础API构建。
.searchToolbarBehavior(.minimize)Tab(role: .search)DefaultToolbarItem(kind: .search)axiom-swiftui-26-refPart 10: API Quick Reference
第十部分:API快速参考
Modifiers
修饰符
| Modifier | iOS | Purpose |
|---|---|---|
| 15+ | Add search field |
| 16+ | Search with tokens |
| 17+ | Tokens + suggested tokens + presentation control |
| 15+ | Auto-fill search on suggestion tap |
| 16+ | Category picker below search |
| 16.4+ | Scopes with activation control |
| 18+ | Programmatic search focus |
| 17.1+ | Keep title visible during search |
| 26+ | Compact/minimize search field |
| 15+ | Handle search submission |
| 修饰符 | iOS版本 | 用途 |
|---|---|---|
| 15+ | 添加搜索栏 |
| 16+ | 带令牌的搜索 |
| 17+ | 令牌+建议令牌+显示控制 |
| 15+ | 点击建议时自动填充搜索栏 |
| 16+ | 搜索栏下方的类别选择器 |
| 16.4+ | 可控制激活时机的搜索范围 |
| 18+ | 程序化搜索焦点控制 |
| 17.1+ | 搜索时保持标题可见 |
| 26+ | 紧凑/最小化搜索栏 |
| 15+ | 处理搜索提交 |
Environment Values
环境值
| Value | iOS | Purpose |
|---|---|---|
| 15+ | Is user actively searching |
| 15+ | Action to dismiss search |
| 值 | iOS版本 | 用途 |
|---|---|---|
| 15+ | 用户是否正在搜索 |
| 15+ | 关闭搜索的操作 |
Types
类型
| Type | iOS | Purpose |
|---|---|---|
| 15+ | Where search field renders |
| 16.4+ | When scopes appear |
| 类型 | iOS版本 | 用途 |
|---|---|---|
| 15+ | 搜索栏的渲染位置 |
| 16.4+ | 搜索范围的激活时机 |
Resources
资源
WWDC: 2021-10176, 2022-10023
Docs: /swiftui/view/searchable(text:placement:prompt:), /swiftui/environmentvalues/issearching, /swiftui/view/searchscopes(:activation::), /swiftui/view/searchfocused(_:), /swiftui/searchfieldplacement
Skills: axiom-swiftui-26-ref, axiom-swiftui-nav-ref, axiom-swiftui-nav
Last Updated Based on WWDC 2021-10176 "Searchable modifier", sosumi.ai API reference
Platforms iOS 15+, iPadOS 15+, macOS 12+, watchOS 8+, tvOS 15+
WWDC:2021-10176, 2022-10023
文档:/swiftui/view/searchable(text:placement:prompt:), /swiftui/environmentvalues/issearching, /swiftui/view/searchscopes(:activation::), /swiftui/view/searchfocused(_:), /swiftui/searchfieldplacement
相关技能:axiom-swiftui-26-ref, axiom-swiftui-nav-ref, axiom-swiftui-nav
最后更新 基于WWDC 2021-10176 "Searchable modifier" 和 sosumi.ai API参考
支持平台 iOS 15+, iPadOS 15+, macOS 12+, watchOS 8+, tvOS 15+