SwiftUIのsearchableモディファイアを使ってみる

アプリで検索機能を実装するときによく使用するのがSearchBarですが、iOS15からSwiftUIに`.searchable`モディファイアが追加されたので使い方などを書いていこうと思います。

https://developer.apple.com/documentation/swiftui/view/searchable(text:placement:)

# 事前に
今回の記事で使用する検索フィールドを追加するViewです。
こちらのコードにsearchableモディファイアなどを追加していきます。

```swift
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`を付与したプロパティを定義します。

```swift
@State private var text = ""
```

次に`NavigationView`にsearchableモディファイアを追加します。

```swift
NavigationView {
    List {
        ForEach(colors, id: \.self) { color in
            Text(color)
        }
    }
    .navigationTitle("Colors")
}
.searchable(text: $text)
```

この状態で動作確認をしてみるとわかると思いますが、最初は検索フィールドが隠れており、スクロールしないと検索フィールドが表示されません。

常に表示されるようにしたい場合は以下のように変更します。

```swift
.searchable(text: $text, placement: .navigationBarDrawer(displayMode: .always))
```

これで常に検索フィールドが表示されるようになりましたが、NavigationViewのタイトルの初期状態が`inline`になり、スクロールなどを行うと`largeTitle`に戻るような動作になってしまうようです。初めから`largeTitle`状態にする方法がわかりませんでした。。

## Listに表示するデータを修正する
入力されているテキストが含まれているデータのみListに表示されるようにコードを追加・修正します。
```swift
var filteredColors: [String] {
    if text.isEmpty {
        return colors
    } else {
        return colors.filter { $0.lowercased().contains(text.lowercased()) }
    }
}
```

```swift
ForEach(filteredColors, id: \.self) { color in
    Text(color)
}
```

## 検索候補を表示する
searchableモディファイアでは、検索フィールドにテキストを入力しているときに検索候補を表示することができる`suggestions:`パラメータがあります。
こちらはViewを返すようになっているので、以下のように実装することができます。

```swift
.searchable(text: $text, placement: .navigationBarDrawer(displayMode: .always)) {
    Text("Red").searchCompletion("Red")
    Text("Purple").searchCompletion("Purple")
    Text("Green").searchCompletion("Green")
}
```
検索候補をタップした際に`searchCompletion(_:)`に指定した文字列が、編集中の検索フィールドのテキストを置き換えます。

## onSubmitモディファイア
今回は使用しませんが、キーボードのリターンキーが押された時などに処理を実行させたい場合に`onSubmitモディファイア`を使用します。

```swift
.onSubmit(of: .search) {
    print("Submit")
}
```

アクションが呼ばれるタイミングは以下の通りです。
- 検索候補がタップされた時
- キーボードのリターンキーが押された時

# ソースコード

```swift
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")
        }
    }
}
```

# 動作確認
![Simulator Screen Recording - iPhone 13 - 2022-08-15 at 17.12.40.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/663839/f04d6111-bdf0-3de3-0e64-938fc30d4b5f.gif)

**検索候補あり**
![Simulator Screen Recording - iPhone 13 - 2022-08-15 at 17.07.22.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/663839/8f00c8e7-3643-d94c-6605-372fbc6450f2.gif)