検索フィールドにおいて提案リストを表示する方法は?searchSuggestionsを上手に活用する

この記事は約14分で読めます。
  • iOS:16.0以上
  • Xcode(当サイトの環境):15.0.1

searchSuggestionsはSwiftUIでsearchable修飾子と一緒に使われ、ユーザーが検索バーにテキストを入力する際に検索候補や提案を表示する機能です。この機能は、ユーザーが検索クエリを素早く入力し、アプリが提供する潜在的な検索オプションを簡単に認識できるようにするために便利です。

基本的な使い方

searchSuggestionsを使用するには、まずsearchable修飾子を使って検索バーを追加します。
その後、searchSuggestionsを追加して、ユーザーに表示したい検索提案を提供します。
ユーザーが検索バーをタップすると、これらの提案がドロップダウンリストとして表示されます。

コード例

import SwiftUI

struct searchSuggestions: View {
    @State private var searchText = ""

    var body: some View {
        NavigationStack {
            Text("Hello World!")
                // Viewのサイズを指定
                .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)
        }
    }
}

この例では、ユーザーが検索バーをタップすると、searchSuggestions内のTextが検索提案として表示されます。
いずれかの検索提案をタップすると画面下部に選択した提案が表示されます。

ポイント

searchSuggestionsを使用するにあたりいくつかポイントがあります。特に❷と❸に関して特殊な動作であり、知らないと不具合のような動作に見えるので注意が必要です。

  • searchableを使用するためにNavigationStackが必要
  • searchSuggestionsを付与するView(上記のコード例ではHello World部分)に覆い被さるように提案リストが表示されるため、searchSuggestionsが表示されている間は元のViewが隠れてしまう
  • searchSuggestionsを付与するViewのサイズに対応して、提案リストが表示されるため、元のViewサイズが小さいと、searchSuggestionsが見えない場合がある

応用:Tokenを活用する検索フィールドにViewを関連づける

Tokenを活用すると検索フィールドにViewを関連づけることも可能です。

コード例

import SwiftUI

struct searchSuggestions: View {
    // Suggestion用のenum
    enum FruitSuggestion: String, Hashable, Identifiable, CaseIterable {
        case Apple
        case Banana
        case Orange
        case Grapes
        case Strawberry
        var id: Self { self }
    }
    
    // 検索文字列を格納
    @State private var searchText = ""
    // Suggestionを格納
    @State private var fruits: [FruitSuggestion] = []
    
    var body: some View {
        NavigationStack {
            Text("Hello World!")
                // Viewのサイズを指定
                .frame(width: 400, height: 500)
                .navigationTitle("フルーツ")
                // 検索バーを追加
                .searchable(text: $searchText, tokens: $fruits) { token in
                    switch token {
                        case .Apple: Text("apple")
                        case .Banana: Text("banana")
                        case .Orange: Text("orange")
                        case .Grapes: Text("grapes")
                        case .Strawberry: Text("strawberry")
                    }
                }
                // 検索候補のリストを表示
                .searchSuggestions {
                    Text("🍎 Apple").searchCompletion(FruitSuggestion.Apple)
                    Text("🍌 Banana").searchCompletion(FruitSuggestion.Banana)
                    Text("🍊 Orange").searchCompletion(FruitSuggestion.Orange)
                    Text("🍇 Grapes").searchCompletion(FruitSuggestion.Grapes)
                    Text("🍓 Strawberry").searchCompletion(FruitSuggestion.Strawberry)
                }
            HStack {
                Text("選択したフルーツ:")
                // 選択したsuggestionを全て表示
                ForEach(fruits, id: \.self) { fruit in
                    Text(fruit.rawValue)
                }
            }
            // 入力した文字列を表示
            Text("入力した文字列:" + searchText)
        }
    }
}

この例では、ユーザーが検索バーをタップすると、searchSuggestions内のTextが検索提案として表示されます。
いずれかの検索提案をタップすると、検索フィールドに関連づけられたViewが表示するとともに画面下部に選択した提案が表示されます。

また、検索フィールドに入力した文字列も同様に画面下部に表示しています。

コードの書き換え

以下のようにsearchSuggestionsを使用せずにコードを書き換えることも可能です。

searchableのパラメータで”suggestedTokens”を指定しています。若干動作が変わってしまいます。
先述のコード例では複数のトークンを指定できていましたが、下記のコードではトークンを1つずつしか指定できません。

あえて使い分けても良いと思います。

コード例

import SwiftUI

struct searchSuggestions: View {
    // Suggestion用のenum
    enum FruitSuggestion: String, Hashable, Identifiable, CaseIterable {
        case Apple
        case Banana
        case Orange
        case Grapes
        case Strawberry
        var id: Self { self }
    }
    
    // 検索文字列を格納
    @State private var searchText = ""
    // Suggestionを格納
    @State private var fruits: [FruitSuggestion] = []
    // Suggestionのリストを格納
    @State private var suggestions: [FruitSuggestion] = FruitSuggestion.allCases
    
    var body: some View {
        NavigationStack {
            Text("Hello World!")
                // Viewのサイズを指定
                .frame(width: 400, height: 500)
                .navigationTitle("フルーツ")
                // 検索バーを追加
                .searchable(text: $searchText, tokens: $fruits, suggestedTokens: $suggestions) { token in
                    switch token {
                        case .Apple: Text("apple")
                        case .Banana: Text("banana")
                        case .Orange: Text("orange")
                        case .Grapes: Text("grapes")
                        case .Strawberry: Text("strawberry")
                    }
                }
            HStack {
                Text("選択したフルーツ:")
                // 選択したsuggestionを全て表示
                ForEach(fruits, id: \.self) { fruit in
                    Text(fruit.rawValue)
                }
            }
            // 入力した文字列を表示
            Text("入力した文字列:" + searchText)
        }
    }
}

実践編:動的に検索候補を更新してフィルターとして使用する

応用の使い方を紹介します。searchSuggestions内でForEachを使用することで、動的に検索候補を表示することも可能です。

以下は検索フィールドの入力内容でフィルターをかけて、検索候補として表示する候補を動的に更新するプログラムになっています。

コード例

import SwiftUI

// 提案リストのStruct
struct SuggestionStruct: Identifiable, Hashable {
    var id: Self { self }
    var label: String
    var image: String
    var completion: String
}


struct searchSuggestions: View {
    // Suggestion用のenum
    var FruitsSuggestion: [SuggestionStruct] = [
        SuggestionStruct(label: "Apple", image: "🍎", completion: "apple"),
        SuggestionStruct(label: "Banana", image: "🍌", completion: "banana"),
        SuggestionStruct(label: "Orange", image: "🍊", completion: "orange"),
    ]
    
    // 検索文字列を格納
    @State private var searchText = ""
    // Suggestionを格納
    @State private var fruits: [SuggestionStruct] = []
    
    var filteredItems: [SuggestionStruct] {
        if searchText.isEmpty {
            return FruitsSuggestion // 検索テキストが空の場合は全ての項目を表示
        } else {
            return FruitsSuggestion.filter { $0.label.localizedCaseInsensitiveContains(searchText) } // 検索テキストに基づいてフィルタリング
        }
    }
    
    var body: some View {
        NavigationStack {
            Text("Hello World!")
                // Viewのサイズを指定
                .frame(width: 400, height: 500)
                .navigationTitle("フルーツ")
                // 検索バーを追加
                .searchable(text: $searchText)
                .searchSuggestions {
                    ForEach(filteredItems) { suggestion in
                        Label(suggestion.label,  image: suggestion.image)
                            .searchCompletion(suggestion.completion)
                    }
                }
            // 入力した文字列を表示
            Text("入力した文字列:" + searchText)
        }
    }
}

この例では、ユーザーが検索バーをタップすると、searchSuggestions内のTextが検索提案として表示されます。

検索フィールドに文字列を入力すると、文字列によってフィルターをかけられた後の検索候補が表示されます。

ちなみにフィルターをかけるコードは以下のページを応用したものです。

まとめ

searchSuggestionsを活用することで、検索フィールドに候補の文字列を表示する方法を紹介しました。

検索候補の表示機能は実際のアプリでも用いられることが多い機能です。ユーザーの操作が便利になる機能であるため、使い方を覚えておくとよいでしょう。

以上、本記事では以下について紹介しました。

  • 検索候補の基本的な表示方法
  • トークンの活用方法
  • 動的に検索候補を更新してフィルターを活用する方法
タイトルとURLをコピーしました