【SwiftUI】PickerとMenuでメニューを表示してみた

2022.08.18

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

現時点でSwiftUIで標準のコンポーネントを使用してメニューを表示するには、PickerpickerStyle(.menu)を使用する方法とMenuを使用する方法があります。

今回は両方のメニューを使って表示してみようと思います。

環境

  • Xcode 13.3

メニューで表示する項目

今回はこのenumをメニューの項目として表示していきます。

enum Food: String, CaseIterable, Identifiable {
    case rice = "Rice"
    case bread = "Bread"
    case hamburger = "Hamburger"

    var id: String { rawValue }

    var emoji: String {
        switch self {
        case .rice:
            return "?"
        case .bread:
            return "?"
        case .hamburger:
            return "?"
        }
    }

    var displayTitle: String {
        return "\(emoji) \(rawValue)"
    }
}

Pickerでメニューを表示する

デモ

picker_menu

コード

import SwiftUI

struct PickerContentView: View {

    @State private var selectedFood = Food.rice

    var body: some View {
        VStack {

            VStack(spacing: 0) {
                Text("注文されたFood")
                Text(selectedFood.emoji)
            }

            Picker("Food", selection: $selectedFood) {
                ForEach(Food.allCases) {
                    Text($0.displayTitle).tag($0)
                }
                .pickerStyle(.menu)
            }
        }
    }
}

Picker

Pickerとは、セットされた相互に排他的な値の選択をコントロールできるものになります。

struct Picker<Label, SelectionValue, Content> where Label : View, SelectionValue : Hashable, Content : View
  • Label
    • Pickerのラベル。このラベルは、FormやList内でPickerを使用すると表示されます。
  • SelectionValue
    • 何が選択されているかの値
  • Content
    • Pickerのコンテンツ

後述するMenuと違って、ラベルに文字列を渡しても、List等と使用しない場合は表示されません。

今回は、ContentとしてFood.allCasesFood.displayTitleの値からTextを作成しています。

ForEach(Food.allCases) {
    Text($0.displayTitle).tag($0)
}

PickerContentのViewのコレクションから現在選択されているものをバインディングする為にはtag(_:) View修飾子を使用して、各選択タイプがバインドされた状態変数のタイプと一致するようにする必要があります。

そして、Pickerをメニュースタイルにする為に、.pickerStyle(.menu)を使用します。

Picker自体は、iOS 13以上からですが、MenuPickerStyleはiOS 14から使用可能となっています。

Menuを使用してメニューを表示する

デモ

menu_menu

コード

import SwiftUI

struct MenuContentView: View {

    @State private var selectedFood = Food.rice

    var body: some View {
        VStack {

            VStack(spacing: 0) {
                Text("注文されたFood")
                Text(selectedFood.emoji)
            }

            Menu("Food") {
                ForEach(Food.allCases) { food in
                    Button {
                        selectedFood = food
                    } label: {
                        Text(food.displayTitle)
                    }
                }
            }
        }
    }
}

Menu

アクションのメニューを表示出来るコントロールになります。

struct Menu<Label, Content> where Label : View, Content : View
  • Label
    • Menuのラベル。Pickerと違い、List等を使用しなくても表示されます。
  • Content
    • Menuのコンテンツ

Pickerの時と同じように、ContentとしてFood.allCasesFood.displayTitleからTextを作成してButtonのラベルにしています。

ForEach(Food.allCases) { food in
    Button {
        selectedFood = food
    } label: {
        Text(food.displayTitle)
    }
}

Pickerの時にようにselectionで選択されたものをバインディングしているわけでなないので今回は選択されたものをButtonのアクションを使用して@State変数に代入しています。

また、Pickerの時とは違い、tagのようなものは必要ありません。

ちなみに今回はラベルを文字列で入力していますが、ラベルにViewを設定することも可能です。

MenuもiOS 14から使用可能となっています。

おわりに

Pickerでメニューを使用する場合と、Menuでメニューを使用する場合とでは若干の見た目の違いと記述の仕方の違いがありました。使用する用途によって使い分けていきたいと思います。

今回実装する中でPickertagを付け忘れていて軽くハマってしまったので、tagのつけ忘れに注意しましょう。笑

参考