【Swift UI】Pickerの書式と使い方とスタイル変更!配列や列挙型

この記事からわかること

  • SwiftPickerとは?
  • 構造書式
  • スタイルセグメント型tag使い方
  • Pickerに列挙型(enum)を指定する方法
  • CaseIterableプロトコルとは?

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

環境

Swift UI:Pickerの構造と書式

【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のポイントまとめ

簡易的なPickerの使い方

@Stateを使ってselectedbodyの外側に宣言しておきます。初期値として値を入れ込んでおけば選択肢のデフォルト値を変更することもできます。

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.countlang.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のポイントまとめ

表示されないラベルを表示する

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として表示させる

NavigationViewnavigationTitleを使って表示させます。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の設定画面のように表示させる

NavigationViewFormを使えばiPhoneの設定画面のようなラベル名と選択項目が並んだレイアウトにすることができます。クリックすると別ページに移動し選択肢を選べるようになります。

【Swift UI】Pickerの例:iPhoneの設定画面のように表示させる
NavigationView{
  Form{
      Picker(selection: $selectedLang, label: Text("言語")) {
          ForEach(lang.indices, id:\.self) { index in
              Text(lang[index])
          }
      }
      
  }
}

Pickerのスタイルを変更する

PickerのスタイルはpickerStyleモディファイアで簡単に変更できます。

SegmentedPickerStyle()

【Swift UI】Pickerの例
Picker(selection: $selectedLang, label: Text("language")) {
  ForEach(lang.indices, id:\.self) { index in
    Text(lang[index])
  }
}.pickerStyle(SegmentedPickerStyle())

なぜかこのスタイルでビルドしようとすると以下のようなエラーが発生しましたが、問題なくビルドすることができました。

SegmentedPickerStyleでビルド時に発生したエラー

WheelPickerStyle()

【Swift UI】Pickerの例
Picker(selection: $selectedLang, label: Text("language")) {
  ForEach(lang.indices, id:\.self) { index in
    Text(lang[index])
  }
}.pickerStyle(WheelPickerStyle())

WheelPickerStyleではアイテムの背景色などを変更することは可能ですがアイテム自体の高さや形を変更することはできないようです。変更したい場合はUIKitのUIPickerViewUIViewRepresentableで実装して以下の設定から変更するしかないかもです。

おすすめ記事:【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プロトコルとはその構造の中に一意となる「識別子: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に画像を使用する

画像を使用する方法は以下の記事を参考にしてください。

【Swift UI】Pickerに画像(Image)を使用する方法!tagモディファイア

UIKitピッカーを実装するには?

UIKitで同じようなピッカービューを実装するにはcode>UIPickerView<< /code>クラスを使用します。詳細について以下の記事を参考にしてください。

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index