This snapshot establishes the camera-to-result recognition flow and related tests while checking in the project skill/docs assets required for the configured local tooling.
738 lines
20 KiB
Markdown
738 lines
20 KiB
Markdown
---
|
|
name: axiom-swiftui-search-ref
|
|
description: Use when implementing SwiftUI search — .searchable, isSearching, search suggestions, scopes, tokens, programmatic search control (iOS 15-18). For iOS 26 search refinements (bottom-aligned, minimized toolbar, search tab role), see swiftui-26-ref.
|
|
license: MIT
|
|
metadata:
|
|
version: "1.0.0"
|
|
---
|
|
|
|
# SwiftUI Search API Reference
|
|
|
|
## Overview
|
|
|
|
SwiftUI search is **environment-based and navigation-consumed**. You attach `.searchable()` 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.
|
|
|
|
#### API Evolution
|
|
|
|
| iOS | Key Additions |
|
|
|-----|---------------|
|
|
| 15 | `.searchable(text:)`, `isSearching`, `dismissSearch`, suggestions, `.searchCompletion()`, `onSubmit(of: .search)` |
|
|
| 16 | Search scopes (`.searchScopes`), search tokens (`.searchable(text:tokens:)`), `SearchScopeActivation` |
|
|
| 16.4 | Search scope `activation` parameter (`.onTextEntry`, `.onSearchPresentation`) |
|
|
| 17 | `isPresented` parameter, `suggestedTokens` parameter |
|
|
| 17.1 | `.searchPresentationToolbarBehavior(.avoidHidingContent)` |
|
|
| 18 | `.searchFocused($isFocused)` for programmatic focus control |
|
|
| 26 | Bottom-aligned search, `.searchToolbarBehavior(.minimize)`, `Tab(role: .search)`, `DefaultToolbarItem(kind: .search)` — see `axiom-swiftui-26-ref` |
|
|
|
|
## 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`.
|
|
|
|
---
|
|
|
|
## Part 1: The searchable Modifier
|
|
|
|
### Core API
|
|
|
|
```swift
|
|
.searchable(
|
|
text: Binding<String>,
|
|
placement: SearchFieldPlacement = .automatic,
|
|
prompt: LocalizedStringKey
|
|
)
|
|
```
|
|
|
|
**Availability**: iOS 15+, macOS 12+, tvOS 15+, watchOS 8+
|
|
|
|
### How It Works
|
|
|
|
1. You attach `.searchable(text: $query)` to a view
|
|
2. The **nearest navigation container** (NavigationStack, NavigationSplitView) renders the search field
|
|
3. The view receives `isSearching` and `dismissSearch` through the environment
|
|
4. 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) }
|
|
}
|
|
}
|
|
```
|
|
|
|
### Placement Options
|
|
|
|
| Placement | Behavior |
|
|
|-----------|----------|
|
|
| `.automatic` | System decides (recommended) |
|
|
| `.navigationBarDrawer` | Below navigation bar title (iOS) |
|
|
| `.navigationBarDrawer(displayMode: .always)` | Always visible, not hidden on scroll |
|
|
| `.sidebar` | In the sidebar column (NavigationSplitView) |
|
|
| `.toolbar` | In the toolbar area |
|
|
| `.toolbarPrincipal` | 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.
|
|
|
|
### Column Association in NavigationSplitView
|
|
|
|
Where you attach `.searchable` determines which column displays the search field:
|
|
|
|
```swift
|
|
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
|
|
```
|
|
|
|
---
|
|
|
|
## Part 2: Displaying Search Results
|
|
|
|
### isSearching Environment
|
|
|
|
```swift
|
|
@Environment(\.isSearching) private var isSearching
|
|
```
|
|
|
|
**Availability**: iOS 15+
|
|
|
|
Becomes `true` when the user activates search (taps the field), `false` when they cancel or you call `dismissSearch`.
|
|
|
|
**Critical rule**: `isSearching` must be read from a **child** of the view that has `.searchable`. SwiftUI sets the value in the searchable view's environment and does not propagate it upward.
|
|
|
|
```swift
|
|
// 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
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### dismissSearch Environment
|
|
|
|
```swift
|
|
@Environment(\.dismissSearch) private var dismissSearch
|
|
```
|
|
|
|
**Availability**: iOS 15+
|
|
|
|
Calling `dismissSearch()` clears the search text, removes focus, and sets `isSearching` to `false`. Must be called from inside the searchable view hierarchy.
|
|
|
|
```swift
|
|
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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Part 3: Search Suggestions
|
|
|
|
### Adding Suggestions
|
|
|
|
Pass a `suggestions` closure to `.searchable`:
|
|
|
|
```swift
|
|
.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.
|
|
|
|
### searchCompletion Modifier
|
|
|
|
`.searchCompletion(_:)` binds a suggestion to a completion value. When the user taps the suggestion, the search text is replaced with the completion value.
|
|
|
|
```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 `.searchCompletion()`**: Suggestions display but tapping them does nothing to the search field. This is the most common suggestions bug.
|
|
|
|
### 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)
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Part 4: Search Submission
|
|
|
|
### 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+
|
|
|
|
### Filter vs Submit Decision
|
|
|
|
| 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 |
|
|
|
|
### 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)
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Part 5: Search Scopes (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+
|
|
|
|
### Scope Activation (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 |
|
|
|------------|----------|
|
|
| `.automatic` | System default |
|
|
| `.onTextEntry` | Scopes appear when user types text |
|
|
| `.onSearchPresentation` | 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
|
|
|
|
---
|
|
|
|
## Part 6: Search Tokens (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 `Identifiable`.
|
|
|
|
### Suggested Tokens (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 `suggestedTokens` and `isPresented` parameters.
|
|
|
|
### Combined 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
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Part 7: Programmatic Search Control (iOS 18+)
|
|
|
|
### searchFocused
|
|
|
|
Bind a `FocusState<Bool>` to the search field to activate or dismiss search programmatically:
|
|
|
|
```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 `.searchFocused(_:equals:)` to match specific focus values.
|
|
|
|
### Comparison with dismissSearch
|
|
|
|
| API | Direction | iOS |
|
|
|-----|-----------|-----|
|
|
| `dismissSearch` | Dismiss only | 15+ |
|
|
| `.searchFocused($bool)` | Activate or dismiss | 18+ |
|
|
|
|
Use `dismissSearch` if you only need to close search. Use `searchFocused` when you need to programmatically *open* search (e.g., a floating action button that opens search).
|
|
|
|
---
|
|
|
|
## Part 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. |
|
|
|
|
### iOS-Specific Behavior
|
|
|
|
```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)
|
|
```
|
|
|
|
### macOS-Specific Behavior
|
|
|
|
```swift
|
|
// Search in toolbar (default on macOS)
|
|
.searchable(text: $searchText, placement: .toolbar)
|
|
|
|
// Search in sidebar
|
|
.searchable(text: $searchText, placement: .sidebar)
|
|
```
|
|
|
|
---
|
|
|
|
## Part 9: Common Gotchas
|
|
|
|
### 1. Search Field Doesn't Appear
|
|
|
|
**Cause**: `.searchable` is not inside a navigation container.
|
|
|
|
```swift
|
|
// WRONG: No navigation container
|
|
List { ... }
|
|
.searchable(text: $query)
|
|
|
|
// CORRECT: Inside NavigationStack
|
|
NavigationStack {
|
|
List { ... }
|
|
.searchable(text: $query)
|
|
}
|
|
```
|
|
|
|
### 2. isSearching Always Returns false
|
|
|
|
**Cause**: Reading `isSearching` from the wrong view level.
|
|
|
|
```swift
|
|
// 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()
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### 3. Suggestions Don't Fill Search Field
|
|
|
|
**Cause**: Missing `.searchCompletion()` on suggestion views.
|
|
|
|
```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
|
|
}
|
|
}
|
|
```
|
|
|
|
### 4. Placement on Wrong Navigation Level
|
|
|
|
**Cause**: Attaching `.searchable` to the wrong column in NavigationSplitView.
|
|
|
|
```swift
|
|
// 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()
|
|
}
|
|
```
|
|
|
|
### 5. Search Scopes Don't Appear
|
|
|
|
**Cause**: Scopes require `.searchable` on the same view. They also require a navigation container.
|
|
|
|
```swift
|
|
// 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)
|
|
}
|
|
```
|
|
|
|
### 6. iOS 26 Refinements
|
|
|
|
For bottom-aligned search, `.searchToolbarBehavior(.minimize)`, `Tab(role: .search)`, and `DefaultToolbarItem(kind: .search)`, see `axiom-swiftui-26-ref`. These build on the foundational APIs documented here.
|
|
|
|
---
|
|
|
|
## Part 10: API Quick Reference
|
|
|
|
### Modifiers
|
|
|
|
| Modifier | iOS | Purpose |
|
|
|----------|-----|---------|
|
|
| `.searchable(text:placement:prompt:)` | 15+ | Add search field |
|
|
| `.searchable(text:tokens:token:)` | 16+ | Search with tokens |
|
|
| `.searchable(text:tokens:suggestedTokens:isPresented:token:)` | 17+ | Tokens + suggested tokens + presentation control |
|
|
| `.searchCompletion(_:)` | 15+ | Auto-fill search on suggestion tap |
|
|
| `.searchScopes(_:_:)` | 16+ | Category picker below search |
|
|
| `.searchScopes(_:activation:_:)` | 16.4+ | Scopes with activation control |
|
|
| `.searchFocused(_:)` | 18+ | Programmatic search focus |
|
|
| `.searchPresentationToolbarBehavior(_:)` | 17.1+ | Keep title visible during search |
|
|
| `.searchToolbarBehavior(_:)` | 26+ | Compact/minimize search field |
|
|
| `onSubmit(of: .search)` | 15+ | Handle search submission |
|
|
|
|
### Environment Values
|
|
|
|
| Value | iOS | Purpose |
|
|
|-------|-----|---------|
|
|
| `isSearching` | 15+ | Is user actively searching |
|
|
| `dismissSearch` | 15+ | Action to dismiss search |
|
|
|
|
### Types
|
|
|
|
| Type | iOS | Purpose |
|
|
|------|-----|---------|
|
|
| `SearchFieldPlacement` | 15+ | Where search field renders |
|
|
| `SearchScopeActivation` | 16.4+ | When scopes appear |
|
|
|
|
---
|
|
|
|
## 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+
|