[SwiftUI]検索バー実装したい我
これを読む。
概要
検索可能なビュー修飾子の 1 つ (searchable(text:placement:prompt:) など) を NavigationSplitView または NavigationStack、あるいはこれらのいずれかの内部のビューに適用して、アプリに検索インターフェースを追加します。
struct ContentView: View {
@State private var departmentId: Department.ID?
@State private var productId: Product.ID?
@State private var searchText: String = ""
var body: some View {
NavigationSplitView {
DepartmentList(departmentId: $departmentId)
} content: {
ProductList(departmentId: departmentId, productId: $productId)
} detail: {
ProductDetails(productId: productId)
}
.searchable(text: $searchText) // Adds a search field.
}
}
NavigationSplitViewであれば、配置場所の設定ができる。
NavigationSplitView {
DepartmentList(departmentId: $departmentId)
} content: {
ProductList(departmentId: departmentId, productId: $productId)
} detail: {
ProductDetails(productId: productId)
}
.searchable(text: $searchText, placement: .sidebar)
Placeholderを変更することができる。
デフォルトは"Search"
DepartmentList(departmentId: $departmentId)
.searchable(text: $searchText, prompt: "Departments and products")
NavigationLinkや、NavigationStackに付与しないと使えないみたい。
検索文字に変更があった場合に、処理をしたい。
onChange
を使用することで、変更時に処理ができるらしい。
struct SearchView: View {
@State private var searchText: String = ""
var body: some View {
NavigationStack {
List {
SearchViewCell(
title: "Swift",
description: "description"
)
}
.searchable(text: $searchText, prompt: "検索")
.onChange(of: searchText, {
// 検索処理
})
}
}
}
onChangeについては以下を参照する。
概要
特定の値が変更されたときにアクションを実行する、このビューの修飾子を追加します。
nonisolated
func onChange<V>(
of value: V,
initial: Bool = false,
_ action: @escaping () -> Void
) -> some View where V : Equatable
・value
はEquatable
を継承している必要がある。
・initial
は、このViewが最初に実行された時に処理を実行するかどうかを決める。
システムはメインアクターでアクション クロージャを呼び出す可能性があるため、クロージャ内で長時間実行されるタスクは避けてください。このようなタスクを実行する必要がある場合は、非同期のバックグラウンド タスクをデタッチします。
struct PlayerView: View {
var episode: Episode
@State private var playState: PlayState = .paused
var body: some View {
VStack {
Text(episode.title)
Text(episode.showTitle)
PlayButton(playState: $playState)
}
.onChange(of: playState) {
model.playStateDidChange(state: playState)
}
}
}
検索ワードを提案する機能があるとのこと。
以下を見ていく。
searchSuggestionsについて
以下は検索フィールドをタップしたら、検索候補が一覧となって表示される。
import SwiftUI
struct searchSuggestions: View {
@State private var searchText = ""
var body: some View {
NavigationStack {
Text("Hello World!")
.frame(width: 400, height: 500)
.navigationTitle("フルーツ")
.searchable(text: $searchText)
.searchSuggestions {
Text("🍎 Apple").searchCompletion("apple")
Text("🍌 Banana").searchCompletion("banana")
Text("🍊 Orange").searchCompletion("orange")
Text("🍇 Grapes").searchCompletion("grapes")
Text("🍓 Strawberry").searchCompletion("strawberry")
}
Text("選択したフルーツ:" + searchText)
}
}
}
SearchCompletionについて
SearchCompletionについて気になったので、以下を参照する。
検索候補として使用される場合、検索トークンをこのビューの値に関連付けます。
nonisolated
func searchCompletion<T>(_ token: T) -> some View where T : Identifiable
token
ビューの補完として使用するデータ。
このメソッドを使用して、検索候補リストのコンテキスト内にあるビューに検索トークンを関連付けます。 ビューが選択されると、システムはこの値を使用して、関連付けられた検索フィールドの現在編集中の部分テキストを置き換えます。
SearchCompletionの設定は、タップしたViewの値をSearchBarに入力されるために設定が必須。
これが設定されていないと、Viewに対して紐づく値がないので、SearchBarの変化はない。
Tokenを活用して、検索フィールドにViewを関連付ける
複数のトークンが入るよう、配列を用意する。
トークンに応じて、検索フィールドにTextが入るようになる。
import SwiftUI
enum FruitToken: Hashable, Identifiable, CaseIterable {
case apple
case pear
case banana
var id: Self { self }
}
struct SearchView: View {
@State private var searchText: String = ""
// 選択済みのTokenを格納する配列
@State private var tokens: [FruitToken] = []
var body: some View {
NavigationStack {
List {
SearchViewCell(
title: "Swift",
description: "description"
)
}
.searchable(text: $searchText, tokens: $tokens, prompt: "検索") { token in
switch token {
case .apple: Text("Apple")
case .pear: Text("Pear")
case .banana: Text("Banana")
}
}
.searchSuggestions {
Text("🍎 Apple").searchCompletion(FruitToken.apple)
Text("🍐 Pear").searchCompletion(FruitToken.pear)
Text("🍌 Banana").searchCompletion(FruitToken.banana)
}
}
}
}
searchSuggestionsを別の方法で指定する
先程とは振る舞いが以下のように異なる
・選択が一つのみ
・検索候補リストには、SwichされたTextViewが表示される
import SwiftUI
enum FruitToken: Hashable, Identifiable, CaseIterable {
case apple
case pear
case banana
var id: Self { self }
}
struct SearchView: View {
@State private var searchText: String = ""
@State private var tokens: [FruitToken] = []
@State private var suggestions: [FruitToken] = FruitToken.allCases
var body: some View {
NavigationStack {
List {
SearchViewCell(
title: "Swift",
description: "description"
)
}
.searchable(
text: $searchText,
tokens: $tokens,
suggestedTokens: $suggestions,
prompt: "検索"
) { token in
switch token {
case .apple: Text("Apple")
case .pear: Text("Pear")
case .banana: Text("Banana")
}
}
}
}
}
検索候補を動的に変える実装もしていて面白い。
公式の内容も読んでおく。
概要
保存した検索テキストとオプションのトークンに基づいて検索結果を更新します。
検索結果を複数のView間で共有する例
struct ContentView: View {
@EnvironmentObject private var model: Model
@State private var departmentId: Department.ID?
@State private var productId: Product.ID?
var body: some View {
NavigationSplitView {
DepartmentList(departmentId: $departmentId)
} content: {
ProductList(departmentId: departmentId, productId: $productId)
.searchable(text: $model.searchText)
} detail: {
ProductDetails(productId: productId)
}
}
}
トークンを作成するには、Identifiable プロトコルに準拠する値のグループを定義し、値のコレクションをインスタンス化します。たとえば、フルーツ トークンの列挙を作成できます。
searchable(text:tokens:placement:prompt:token:)
について公式見てみる。
概要
このビューをテキストとトークンで検索可能としてマークします。
nonisolated
func searchable<C, T, S>(
text: Binding<String>,
tokens: Binding<C>,
placement: SearchFieldPlacement = .automatic,
prompt: S,
@ViewBuilder token: @escaping (C.Element) -> T
) -> some View where C : RandomAccessCollection, C : RangeReplaceableCollection, T : View, S : StringProtocol, C.Element : Identifiable
token:
検索フィールドで表示および編集するトークンのコレクション。
token:
トークン内の要素を指定してビューを作成するビュー ビルダー。
tokenは、@ViewBuilder
が付与されていて、Tokenに応じてTextViewなど設定することができる。
SuggestionするときのViewはここで作られているのかな。
まとめ
・NavigationStack
とかを使用してないと使えない
・.searchable()
を使用する
・onChange
を使用して変更時の処理を記述できる
・検索補完機能が使える