SwiftUIでSearchBarが使いたいので自作する

2022.04.22

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

SwiftUIでUISearchBarのようなものが使いたかったので自作することにしました。

環境

  • Xcode 13.3

はじめに

2022年4月時点でSearchBarというようなViewはSwiftUIの標準では存在しておりません。その代わりiOS 15以降ではsearchable(text:placement:)というモディファイアを使うことができ、SearchBarのような動きをしてくれます。

iOS 15からという条件もそうなのですが、2022年4月22日時点ではBetaの記載があり、またこのモディファイアはNavigationViewをラップしなければ表示できません。

NavigationView無し

struct ContentView: View {

    @State private var searchText = ""

    var body: some View {
        Text("SearchBar↑")
            .searchable(text: $searchText)
    }
}

結果

SearchBar表示されません。

NavigationView有り

struct ContentView: View {

    @State private var searchText = ""

    var body: some View {
        NavigationView {
            Text("SearchBar↑")
                .searchable(text: $searchText)
        }
    }
}

結果

SearchBarが表示されました。

これで検索を行えるようになりました!しかし、NavigationViewをラップしないと使えないというのは少し不便ですね。

NavigationViewがない場所でも気軽にSearchBarを気軽に使いたいので自分で作ってみることにしました。

import SwiftUI

struct SearchBar: View {

    @Binding var text: String

    var body: some View {
        VStack {

            ZStack {
                // 背景
                RoundedRectangle(cornerRadius: 8)
                    .fill(Color(red: 239 / 255,
                                green: 239 / 255,
                                blue: 241 / 255))
                    .frame(height: 36)

                HStack(spacing: 6) {
                    Spacer()
                        .frame(width: 0)

                    // 虫眼鏡
                    Image(systemName: "magnifyingglass")
                        .foregroundColor(.gray)

                    // テキストフィールド
                    TextField("Search", text: $text)

                    // 検索文字が空ではない場合は、クリアボタンを表示
                    if !text.isEmpty {
                        Button {
                            text.removeAll()
                        } label: {
                            Image(systemName: "xmark.circle.fill")
                                .foregroundColor(.gray)
                        }
                        .padding(.trailing, 6)
                    }
                }
            }
            .padding(.horizontal)
        }
    }
}

標準のsearchableと比較してみましょう。

上側が、標準のSearchableで、下側が自作のSearchBarです。

見た目的にも同じように作成出来ました!しかし、プレースホルダーのカラーの違いが少し気になるので変えていきます。

プレースホルダーのカラーを変更する

2022年4月時点では、プレースホルダーの色を変更するAPIはまだ無さそうなので自作のプレースホルダーモディファイアを作成します。

extension View {
    func placeholder<Content: View>(
        when shouldShow: Bool,
        alignment: Alignment = .leading,
        color: Color,
        @ViewBuilder placeholder: () -> Content) -> some View {

        ZStack(alignment: alignment) {
            placeholder()
                .opacity(shouldShow ? 1 : 0)
                .foregroundColor(color)
            self
        }
    }
}
  • when shouldShow
    • いつプレースホルダーを表示するか
  • alignment
    • プレースホルダーのalignmentです。基本的には.leadingでいいと思います。
  • color
    • プレースホルダーのカラー
  • @ViewBuilder placeholder
    • placeHolderとして使用するViewを受け取ります。

自作プレースホルダーを使う

下記のようにTextFieldと一緒に使用します。TextFieldの元々のプレースホルダーは使用しないので空にしておき、自作したプレースホルダーのコンテンツとしてText("Search")を入れています。colorは標準Searchableのプレースホルダーの色味に近かった.grayにしました。

TextField("", text: $text)
    .placeholder(when: text.isEmpty, color: .gray) {
        Text("Search")
    }

標準のsearchableと比較してみましょう。

さらに標準Searchableの雰囲気に近づけることが出来ました。

完成版

完成版のコードはGitHubに上げています。

動かすとこんな感じになります。searchableにそっくりな自家製のSearchBarを作ることが出来ました!

おわりに

ライブラリにする必要まではなさそうですが、時折使えそうなのでコードスニペットとして登録しておいて使いたい時に瞬時に使えるようにすると良さそうですね。

参考