【Swift UI】Pickerの書式と使い方とスタイル変更!配列や列挙型
この記事からわかること
- SwiftのPickerとは?
- 構造や書式
- スタイルやセグメント型、tagの使い方
- Pickerに列挙型(enum)を指定する方法
- CaseIterableプロトコルとは?
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
環境
- Xcode:15.0.1
- iOS:17.0
- Swift:5.9
- macOS:Sonoma 14.1
Swift UI:Pickerの構造と書式
Swift UIをのPicker(ピッカー)を使えば画像のような項目を選択できる表示を簡単に実装することができます。
書式
@State var selected = 0
Picker(selection: $selected, label: Text("ラベル")) {
Text("選択肢1").tag(0)
Text("選択肢2").tag(1)
Text("選択肢3").tag(2)
}
引数の中には選択されたインデックス番号(値)が格納されるselection
と表示されるラベルを定義できるlabel
が指定できます。しかしPickerのラベル部分はSwift. Xcode 12.2頃から仕様かバグにより表示されないようです。selection
は値を変更できるように@State
をつけて宣言しておきます。
選択肢はText()
などを使って表示部分を列挙していきます。.tag(数値)
をつけることで選択肢のインデックス番号を明示的に指定することができます。インデックス番号の並び順を変更しても実際に表示される順番はコードの順番になるので注意してください。
Pickerのポイントまとめ
- 引数selectionは@Stateをつけて宣言
- 引数selectionに選択された値が格納される
- ラベル部分はそのままでは表示されない
- .tag()でインデックス番号を指定できる
- .tag()で指定した順番と表示される順番は一致しない
簡易的なPickerの使い方
@State
を使ってselected
をbody
の外側に宣言しておきます。初期値として値を入れ込んでおけば選択肢のデフォルト値を変更することもできます。
struct ContentView: View {
@State var selected = 0 // 選択肢の初期値
var body: some View {
VStack {
Picker(selection: $selected, label: Text("スポット")) {
Text("水族館").tag(0)
Text("図書館").tag(1)
Text("遊園地").tag(2)
Text("体育館").tag(3)
}
Text("選択したインデックス番号:\(selected)") // 選択したインデックス番号: 0
}
}
}
selected
に格納されているのはインデックス番号なので選択肢の値を表示させることはできません。また.tag(数値)
をつけ忘れるとselected
には何も格納されないので注意してください。(この場合ずっと初期値0のまま)
配列を使った使い方
選択された値を表示させるには配列を使うと便利です。配列なのでForEach
を使って簡単に表示させる選択肢を構築することも可能です。また..tag()
の指定も不要になります。
struct ContentView: View {
@State var selectedLang = 0
let lang = ["HTML", "CSS", "PHP", "Swift", "C"]
var body: some View {
VStack {
Picker(selection: $selectedLang, label: Text("language")) {
ForEach(0..<lang.count) { index in
Text(lang[index]) // .tagは不要になる
}
}
Text("選択した値:\(lang[selectedLang])") // 選択した値: HTML
}
}
}
あとは配列の中から選択されたインデックス番号と同じ番号の要素を表示させればOKです。
ForEach分の構文
ForEach
は配列の範囲を指定する際の構文が少し異なるのでそれによってPickerを扱う時も注意が必要です。
範囲は0..<配列の要素数
までなので配列.count
を使って指定します。
ForEach(0..<lang.count) { index in
Text(lang[index])
}
しかしこの構文ではNon-constant range: argument must be an integer literal
という警告が出てしまいます。これを防ぐためにはid : \.self
を追加します。また0..<lang.count
はlang.indices
で表現できます。
ForEach(lang.indices, id:\.self) { index in
Text(lang[index])
}
さらにlang.indices
とせずとも配列をそのまま指定してもOKです。その際はselectedLang
部分にインデックス番号ではなく値(文字列)自体が格納されます。
ForEach(lang, id: \.self) { item in
Text(item)
}
なので@State
で宣言しているselectedLang
はインデックス番号ではなく初期値に設定する文字列をそのまま格納しておきます。
配列をそのまま指定する場合
struct ContentView: View {
@State var selectedLang = "HTML" // 初期値に文字列を指定する
let lang = ["HTML", "CSS", "PHP", "Swift", "C"]
var body: some View {
VStack {
Picker(selection: $selectedLang, label: Text("language")) {
ForEach(lang, id:\.self) { item in
Text(item)
}
}
// selectedLangに選択された値が入る
Text("選択した値:\(selectedLang)") // 選択した値: HTML
}
}
}
配列を使用するPickerのポイントまとめ
- ForEachを使って配列を選択肢にできる
- .tag()が不要になる
- 範囲は「0..<配列.count」や「配列.indices」、「配列」をそのまま指定
- Non-constant range警告抑制のため「id:\.self」
- 配列を直接渡す場合はselectionに番号ではなく値を格納
表示されないラベルを表示する
Swift. Xcode 12.2頃から仕様かバグかでデフォルトで表示されなくなってしまったらしいので表示させる方法をみていきます。元々は非表示にする際は.labelsHidden
を指定していました。
Text(ラベル)として表示させる
まずは普通にText(ラベル)
として表示させます。VStack
などで配置を調整してあげます。
VStack {
Text("スポット")
Picker(selection: $selected, label: Text("スポット")) {
Text("水族館").tag(0)
Text("図書館").tag(1)
Text("遊園地").tag(2)
Text("体育館").tag(3)
}
}
navigationTitleとして表示させる
NavigationView
のnavigationTitle
を使って表示させます。Pickerに対して指定しないと表示されないので注意してください。
NavigationView{
Picker(selection: $selectedLang, label: Text("language")) {
ForEach(lang.indices, id:\.self) { index in
Text(lang[index])
}
}.navigationTitle(Text("スポット"))
}
Sectionとして表示させる
Section
のヘッダーとして表示させるにはVStack
で囲んでおきます。
VStack{
Section(header:Text("スポット").font(.headline)){
Picker(selection: $selectedLang, label: Text("language")) {
ForEach(lang.indices, id:\.self) { index in
Text(lang[index])
}
}
}
}
iPhoneの設定画面のように表示させる
NavigationView
とForm
を使えばiPhoneの設定画面のようなラベル名と選択項目が並んだレイアウトにすることができます。クリックすると別ページに移動し選択肢を選べるようになります。
NavigationView{
Form{
Picker(selection: $selectedLang, label: Text("言語")) {
ForEach(lang.indices, id:\.self) { index in
Text(lang[index])
}
}
}
}
Pickerのスタイルを変更する
PickerのスタイルはpickerStyle
モディファイアで簡単に変更できます。
SegmentedPickerStyle()
Picker(selection: $selectedLang, label: Text("language")) {
ForEach(lang.indices, id:\.self) { index in
Text(lang[index])
}
}.pickerStyle(SegmentedPickerStyle())
なぜかこのスタイルでビルドしようとすると以下のようなエラーが発生しましたが、問題なくビルドすることができました。
WheelPickerStyle()
Picker(selection: $selectedLang, label: Text("language")) {
ForEach(lang.indices, id:\.self) { index in
Text(lang[index])
}
}.pickerStyle(WheelPickerStyle())
WheelPickerStyle
ではアイテムの背景色などを変更することは可能ですがアイテム自体の高さや形を変更することはできないようです。変更したい場合はUIKitのUIPickerView
をUIViewRepresentable
で実装して以下の設定から変更するしかないかもです。
おすすめ記事:【SwiftUI】UIViewRepresentableの使い方!Coordinatorクラスとは?
// アイテムの高さ
func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat {
return 50
}
※watchOSならdefaultWheelPickerItemHeight
メソッドで高さを変更することができます。
枠線と背景色をつける
Pickerに枠線や背景色をつけたい場合はViewのモディファイアを使用することで簡単に変更できます。
Picker(selection: $selectedLang, label: Text("language")) {
ForEach(lang.indices, id:\.self) { index in
Text(lang[index])
}
}.border(.orange) // 枠線
.background(.gray) // 背景色
.tint(.white) // 文字色
文字色を変更したい場合はtint
を使用します。
角を丸めた枠線
角を丸めた枠線はborderではできないので以下のようにすることで実装できます。
Picker(selection: $selectedLang, label: Text("language")) {
ForEach(lang.indices, id:\.self) { index in
Text(lang[index])
}
}.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(.orange.opacity(0.4), lineWidth: 2)
)
データに列挙型(enum)を指定する方法
Picker
で表示するデータに列挙型を渡すにはポイントとなる2つのプロトコルを指定します。
列挙型(enum)を指定する際のポイント
- Identifiableプロトコル
- CaseIterableプロトコル
Identifiable
プロトコルとはその構造の中に一意となる「識別子:identifier」を定義することを義務付けるプロトコルです。これに倣ってidプロパティ
を追加で定義します。
enum Spot:String,Identifiable ,CaseIterable{
var id:String{self.rawValue}
case house // 人の家
case restaurant // 飲食店
case workplace // 仕事場
case shop // ショップ
case facility // 施設
case leisure // レジャー
case nature // 自然
case parking // 駐車場
}
CaseIterableプロトコルとは?
CaseIterable
プロトコルはSwift 4.2から使用可能になったプロトコルで定義されているプロパティの値をコレクション形式で取得できるallCases
プロパティが実装されます。
print(Spot.allCases)
// [__lldb_expr_1.Spot.house, __lldb_expr_1.Spot.restaurant, __lldb_expr_1.Spot.workplace, __lldb_expr_1.Spot.shop, __lldb_expr_1.Spot.facility, __lldb_expr_1.Spot.leisure, __lldb_expr_1.Spot.nature, __lldb_expr_1.Spot.parking]
あとはこれを組み合わせてPicker
の構文に当てはめるだけです。
Picker(selection: $selectedSpot, label: Text("スポット")) {
ForEach(Spot.allCases, id: \.self) { item in
Text(item.rawValue)
}
}
Pickerに画像を使用する
画像を使用する方法は以下の記事を参考にしてください。
UIKitピッカーを実装するには?
UIKitで同じようなピッカービューを実装するにはcode>UIPickerView<< /code>クラスを使用します。詳細について以下の記事を参考にしてください。
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。