【SwiftUI】Pickerの使い方|選択ピッカーメニューを作る

本記事では、SwiftUIの「Picker」の使い方について見ていきます。

Pickerとは、複数の選択項目の中から一つを選ぶことができるUI部品のことを指します。

Pickerは簡易的なコードで選択メニューを実装することができますが、ビルドは通るが機能が正常に動かない実装パターンもいくつか存在したりします。

本記事ではそういった実装のアンチパターンについてもご紹介します。

ソースコード

本記事で使用しているサンプルコードはGitHubにて公開されています。

コード内容の確認にぜひご利用ください🍎

ソースコードを見る>>

[ 本記事はこんな人におすすめ ]

・SwiftUIでアプリ開発をしている

・複数項目から選択するピッカーメニューを作成したい

・「Picker」の使い方やオプションについて知りたい

				
					// Pickerのシンプルなイニシャライザ
init<S>(_ title: S, selection: Binding<SelectionValue>, content: () -> Content) where S : StringProtocol
				
			

【パラメータの内容】

title:

 Pickerのラベル(String)。特定のピッカースタイルでのみ表示される。

selection:

 選択アイテムを保持する@Stateプロパティへのバインディング。

content:

 Pickerの選択要素。クロージャ。

Pickerの使い方

シンプルな実装コード

以下のコードは、単純なカラーの選択メニューをPickerで実装したシンプルな例です。

				
					/// シンプルなPickerの実装コード
struct BasicPicker: View {

    // 現在の選択アイテムを保持
    @State private var selectionColor: Color = .red

    var body: some View {

        VStack {
            Picker("色を選択", selection: $selectionColor) {
                Text("赤").tag(Color.red)
                Text("青").tag(Color.blue)
                Text("緑").tag(Color.green)
            }
            // 選択カラーを表示
            selectionColor
                .frame(width: 300, height: 300)
        }
    }
}
				
			

ピッカーからアイテムが選択されると、アイテムに付与している.tag()の値が、引数「selection:」に紐づけた@Stateプロパティにセットされます。

撮影の環境によりメニューの影が少し乱れております🙇‍♂️

ラベルに「“色を選択”」が入力されていますが、このラベルは一定のピッカースタイル時でのみ画面に表示されます。

スタイルの変更については後述【Pickerのスタイル】で解説します。

なぜ「.tag」が必要なのか

.tagメソッドは、特定の選択可能なビューの実装(Picker、 TabViewなど)において、各ビューを区別するための値を付与することができます。

ドキュメント👉|tag(_:) | Apple Developer Documentation

前項目で紹介したシンプルな実装コードでは、選択要素として配置したTextに対して.tagを付与していました。

TextというViewパーツ自体は、自身を区別するための識別値を持っていないため、.tagを付与して一意(ユニーク)の識別値を持たせています。

試しに先ほどのコード例から.tagを外してみると、データ切り替えが正常に動作しなくなることがわかります。

他の項目を選択しても、初期値の「赤」から変化しない

Pickerに限らず、複数項目から選択しデータの切り替えを行う実装において期待する動作をしない時、識別値(id)の設定が適切でないことが原因である場合があります。

「あれ、正常にデータが切り替わらないぞ…??」という場面にもし出会ったら、SwiftUIがデータを区別するための識別値について、チェックしてみてください。

扱うデータが自身を識別するための一意の値を保証できていれば、.tagの付与をしなくても正常に動作します(ForEachでidが定義されているなど)。

idを持つデータのピッカー実装については、後項目【カスタムデータを使ったPicker】にて、詳しく解説しています。

Pickerのスタイル

ピッカーは修飾子によってスタイルを切り替えることができます。

各スタイルの外観を以下に挙げます。

メニュー型(.menu)

デフォルトで設定されているピッカースタイルです。

ラベル表示なし。

ホイール型(.wheel)

縦スクロールによって選択できるホイールピッカースタイルです。

ラベル表示なし。

セグメント型(.segmented)

選択肢が横並びに表示されるピッカースタイルです。

ラベル表示なし。

ListやFormと組み合わせたスタイル(.inline)

.inlineスタイルは、ListFormと組み合わせることで使用できるスタイルです。

このスタイルの場合、Pickerに設定したラベルが表示されます。

各選択要素がラインによって区切られ、選択中のデータはチェックマークによって表現されます。

上記の実装ではFormが使用されていますが、Listでも同様のレイアウトになります。

カスタムデータを用いたPicker

カスタム構造体(struct)による実装

Pickerは、独自に作成したカスタム構造体(struct)を扱うことも可能です。

以下は、独自の構造体「Fruit(フルーツ)」を使って、フルーツ選択ピッカーメニューを実装したコード例です。

				
					/// カスタムデータ構造体(フルーツ)
struct Fruit: Identifiable, Hashable {
    var name: String
    var symbol: String
    var id: String { self.name }
}

/// 独自のカスタムデータを用いたピッカーの実装
struct CustomDataPicker: View {

    // カスタムデータ群
    let fruits: [Fruit] = [
        Fruit(name: "バナナ", symbol: "🍌"),
        Fruit(name: "りんご", symbol: "🍎"),
        Fruit(name: "キウイ", symbol: "🥝")
    ]
    // 選択されている現在のアイテム
    // 選択アイテムのidが格納される
    @State private var selectionFruit: String = ""

    var body: some View {
        VStack {
            Picker("", selection: $selectionFruit) {
                ForEach(fruits) { fruit in
                    Text("\(fruit.symbol) \(fruit.name)")
                }
            }
        }
    }
}
				
			

「id」を持つデータを扱う場合の識別値について

Pickerが扱うデータがパラメータに「id」を持っている & .tagによる識別値の指定がない時、SwiftUIはデータのidを自動で識別値として使用し、@Stateプロパティに格納します。

自身の「name:」の値をidとして設定している

上記の実装であれば、自身の「name:」の値をidとして用いているため、りんごを選択すると@Stateプロパティに「“りんご”」が格納されます。

@Stateプロパティの中身をonChangeモディファイアで確認してみると、以下のように、ピッカーで選択されたアイテムの「name:」の値が保持されていることがわかります。

【自身のidをnameで設定した場合】

ピッカー選択によって要素のname値が保持される様子

では次に、idの値として自身の「symbol:」の値を用いるパターンを試してみます。

自身の「symbol:」の値をidとして設定している

この場合、りんごを選択すると@Stateプロパティに「“🍎”」が格納されます。

@Stateプロパティの中身をonChangeモディファイアで確認してみると、以下のように、ピッカーで選択されたアイテムの「symbol:」の値が保持されていることがわかります。

【自身のidをsymbolで設定した場合】

ピッカー選択によって要素のsymbol値が保持される様子

また、これらのようにアイテムがidを持っている場合であっても、Pickerの中でさらにtagが設定されていた場合、tagの値が識別値として優先的に保持されます。

以下のコード例では、「symbol:」値を自身のidとして持っているアイテムに対して、Picker内でさらに.tag(fruit.name)を付与しています。

この場合、@Stateプロパティにはアイテムの「name:」値が格納されます。

【アイテムがidを持つ & tagも設定している場合】

tagに設定した値が優先的に識別値として保持される

扱うデータのidは、各データごとで一意(ユニーク)の値である必要があります。

コード例では単純に、自身の「name:」や「symbol:」値をidとして設定していますが、使用ケースによってはUUIDなどを使って一意の値となるよう設定してください。

列挙体(enum)による実装

現在の選択アイテムを保持することでピッカーの状態を管理するPickerの機能は、列挙体(enum)との相性がとても良いです。

以下は、複数の果物データを管理する列挙体「Fruits」を用いたPickerの実装コード例です。

enumを「CaseIterable」プロトコルに準拠させることで、enumの全てのケースをコレクションとして返す「.allCasesを使うことができるようになります。

ドキュメント👉|allCases | Apple Developer Documentation

これにより、enumに定義されている全てのケースをForEachに渡すことができます。

				
					import SwiftUI

/// フルーツの列挙体
/// CaseIterableプロトコルに準拠することで、「.allCases」を使用できる
enum Fruits: CaseIterable {
    case apple
    case orange
    case melon

    // 名前
    var name: String {
        switch self {
        case .apple : return "りんご"
        case .orange: return "みかん"
        case .melon : return "メロン"
        }
    }
    // シンボル
    var symbol: String {
        switch self {
        case .apple : return "🍎"
        case .orange: return "🍊"
        case .melon : return "🍈"
        }
    }
}

/// 列挙体(enum)を用いたPickerの実装
struct EnumPicker: View {

    // 列挙体のデータを格納
    @State private var selectionFruit = Fruits.apple

    var body: some View {
        VStack {
            Picker("", selection: $selectionFruit) {
                // 「allcases」を使って、enumの各ケースをコレクションで渡す
                ForEach(Fruits.allCases, id: \.self) { fruit in
                    Text(fruit.name)
                }
            }
            // 選択アイテムの表示
            Text(selectionFruit.symbol)
                .font(.largeTitle)
        }
    }
}
				
			

enumにプロパティ(例: 名前、シンボル)を持たせ、各ケースごとで返す値をswitch文で設定しておきます。

@Stateプロパティはenumのデータ型として用意し、ケースの一つを初期値として渡します。

これにより、簡単にデータの切り替えを管理することができます。

また、各要素の内容をenumの中で一元管理できるため、可読性が高く、後から要素の組み替えがやり易いのも大きなポイントです。

Pickerが正しく動作しない実装パターン

Pickerは実装時にいくつか気を付けるポイントがあり、適切に実装できていないと以下のようにピッカーメニューのデータが正常に切り替わらない場合があります。

他の項目を選択しても、データが切り替わらない

また、ここで紹介するアンチパターンの全てが、ビルド自体は通ってしまうというのが、不具合の原因を掴みづらくしている要因にもなっています。

以下に、Pickerが正しく動作しなくなるアンチパターン例を挙げます。

データに識別値(id)が設定されていない

Pickerが扱うデータに識別値が設定されていない場合、SwiftUIがデータの判別を出来ず、ピッカーの選択が正常に動作しません。

以下は、識別値を持たない単純なTextビューをデータとして渡した場合の挙動です。メニュー内のどの項目を選択してもデータが切り替わらないことがわかります。

				
					@State private var selectionColor: Color = .red

var body: some View {

    VStack {
        Picker("色を選択", selection: $selectionColor) {
        
            /// Text自体は識別値を持たない❌
            Text("赤")
            Text("青")
            Text("緑")
        }
    }
}
				
			
他の項目を選択しても、初期値の「赤」から変化しない

こちらのようなパターンは、

 ・.tag()で一意の識別値を与える

 ・カスタムデータに「id:」パラメータを与えておく

 ・ForEathを組み込む際にidを定義する

などで識別値を設定することで、回避することができます。

データの識別値と@Stateプロパティのデータ型が異なる

Pickerに紐付ける@Stateプロパティと、扱うデータの識別値のデータ型が異なる場合、ピッカーは正常に動作しません。

以下は、データ型の不一致が起きているコードの実装例です。

				
					// String型で定義されたStateプロパティ
@State private var selectionValue: String = ""

var body: some View {

    VStack {
        Picker("色を選択", selection: $selectionValue) {
            /// .tag()のデータ型がStateプロパティのデータ型と異なる❌
            Text("赤").tag(Color.red)
            Text("青").tag(Color.blue)
            Text("緑").tag(Color.green)
        }
    }
}
				
			

@Stateプロパティをオプショナルで定義している

Pickerに紐付ける@Stateプロパティをオプショナルで定義している場合、ピッカーは正常に動作しません。

Pickerに用いる保持プロパティは非オプショナルとして定義する必要があります。

				
					// オプショナルで定義している❌
@State private var selectionColor: Color?

var body: some View {

    VStack {
        Picker("色を選択", selection: $selectionColor) {

            Text("赤").tag(Color.red)
            Text("青").tag(Color.blue)
            Text("緑").tag(Color.green)
        }
    }
}
				
			

まとめ

以上、SwiftUIの「Picker」についてでした!

Pickerの要点を以下にまとめます。

ピッカー型の選択メニューを実装できる

扱うデータは、それぞれを区別する一意の識別値が必要

・複数種類の外観スタイルから選択可能

・ピッカーが正しく動作しないアンチパターンに注意

本記事が参考になれば幸いです👍

\  SHARE  /

Twitter
Facebook
Email

✏️ アプリ開発が学べる勉強会を開催中! 

CodeCandyではアプリ開発を学ぶための勉強会を定期開催しています。
学習する習慣を身につけたい、他の参加者と作業したい、アプリ開発の基本をマスターしたい、という方のために無料で学べる勉強会です。
グループにメンバー登録して頂くと、イベント開催時にメールで通知されます。

▶️グループのメンバーとして参加する

徹底した基礎学習からマスターするiPhoneアプリ開発集中オンライン講座開講!

本書「iPhoneアプリ開発集中講座」を執筆している現役エンジニア講師陣が直接に指導!
基礎、課題実習で実践力を鍛えて、オリジナルアプリ公開までチャレンジ!
充実した転職支援もあるので、エンジニアへ転職したい人にもおすすめです!

まずは、現役エンジニアに相談できる無料相談をご利用ください。

By 中川 賢亮

2022年2月よりSwift学習を始め、4月からiOSアプリ開発オンラインスクール「CodeCandy」にてアプリ開発を学ぶ。 2023年10月に個人開発アプリ「unico」をリリース。現在はアプリの機能アップデートをしながら、スクール運営の技術ブログの執筆や、出版書籍の入稿チェック・デバッグにも携わる。