アプリで検索機能を実装するときによく使用するのがSearchBarですが、iOS15からSwiftUIに.searchable
モディファイアが追加されたので使い方などを書いていこうと思います。
事前に
今回の記事で使用する検索フィールドを追加するViewです。
こちらのコードにsearchableモディファイアなどを追加していきます。
struct ContentView: View {
let colors: [String] = ["Red", "Blue", "Yellow", "Purple", "Orange", "Green", "Pink", "White", "Gray", "Black"]
var body: some View {
NavigationView {
List {
ForEach(colors, id: \.self) { color in
Text(color)
}
}
.navigationTitle("Colors")
}
}
}
searchableモディファイアのパラメータ
text
検索フィールドに表示、編集するテキスト
placement
検索フィールドの配置場所を設定する
実装
初めに検索フィールドに入力したテキストを保持するための@State
を付与したプロパティを定義します。
@State private var text = ""
次にNavigationView
にsearchableモディファイアを追加します。
NavigationView {
List {
ForEach(colors, id: \.self) { color in
Text(color)
}
}
.navigationTitle("Colors")
}
.searchable(text: $text)
この状態で動作確認をしてみるとわかると思いますが、最初は検索フィールドが隠れており、スクロールしないと検索フィールドが表示されません。
常に表示されるようにしたい場合は以下のように変更します。
.searchable(text: $text, placement: .navigationBarDrawer(displayMode: .always))
これで常に検索フィールドが表示されるようになりましたが、NavigationViewのタイトルの初期状態がinline
になり、スクロールなどを行うとlargeTitle
に戻るような動作になってしまうようです。初めからlargeTitle
状態にする方法がわかりませんでした。。
Listに表示するデータを修正する
入力されているテキストが含まれているデータのみListに表示されるようにコードを追加・修正します。
var filteredColors: [String] {
if text.isEmpty {
return colors
} else {
return colors.filter { $0.lowercased().contains(text.lowercased()) }
}
}
ForEach(filteredColors, id: \.self) { color in
Text(color)
}
検索候補を表示する
searchableモディファイアでは、検索フィールドにテキストを入力しているときに検索候補を表示することができるsuggestions:
パラメータがあります。
こちらはViewを返すようになっているので、以下のように実装することができます。
.searchable(text: $text, placement: .navigationBarDrawer(displayMode: .always)) {
Text("Red").searchCompletion("Red")
Text("Purple").searchCompletion("Purple")
Text("Green").searchCompletion("Green")
}
検索候補をタップした際にsearchCompletion(_:)
に指定した文字列が、編集中の検索フィールドのテキストを置き換えます。
onSubmitモディファイア
今回は使用しませんが、キーボードのリターンキーが押された時などに処理を実行させたい場合にonSubmitモディファイア
を使用します。
.onSubmit(of: .search) {
print("Submit")
}
アクションが呼ばれるタイミングは以下の通りです。
- 検索候補がタップされた時
- キーボードのリターンキーが押された時
ソースコード
import SwiftUI
struct ContentView: View {
let colors: [String] = ["Red", "Blue", "Yellow", "Purple", "Orange", "Green", "Pink", "White", "Gray", "Black"]
var filteredColors: [String] {
if text.isEmpty {
return colors
} else {
return colors.filter { $0.lowercased().contains(text.lowercased()) }
}
}
@State private var text = ""
var body: some View {
NavigationView {
List {
ForEach(filteredColors, id: \.self) { color in
Text(color)
}
}
.navigationTitle("Colors")
}
.searchable(text: $text, placement: .navigationBarDrawer(displayMode: .always)) {
Text("Red").searchCompletion("Red")
Text("Purple").searchCompletion("Purple")
Text("Green").searchCompletion("Green")
}
.onSubmit(of: .search) {
print("Submit")
}
}
}