⚒️

SwiftUIで複数のアラートを一つのViewで管理する

はじめに

SwiftUIで複数のタイプのアラートを実装する場面はよくありますが、このようにアラートを実装するとうまく動作しません。

struct ContentView: View {
    @State private var showInfoAlert = false
    @State private var showWarningAlert = false

    var body: some View {
        VStack {
            Button("情報アラート") {
                showInfoAlert = true
            }
            Button("警告アラート") {
                showWarningAlert = true
            }
        }
        .alert("情報", isPresented: $showInfoAlert) {
            Button("OK", role: .cancel) { }
        } message: {
            Text("これは情報アラートです。")
        }
        .alert("警告", isPresented: $showWarningAlert) {
            Button("了解", role: .cancel) { }
        } message: {
            Text("これは警告アラートです。")
        }
    }
}

複数の.alert修飾子を同じビューに適用すると、SwiftUIのビュー階層の仕組みによって最後に定義されたアラートしか動作しなくなってしまいます。この例では「警告」アラートのみが機能し、「情報」アラートはボタンを押しても表示されず、無視されてしまいます。

解決策

単一".alert"と表示用のフラグを使用してアラートの表示状態を管理しつつ、enumを使用して異なるアラートを出し分ける方法がわかりやすい解決策の1つです。

import SwiftUI

struct ContentView: View {
    @State private var showAlert = false
    @State private var alertType: AlertType = .informational

    enum AlertType {
        case informational
        case warning
        case confirmation
        case error
    }

    var body: some View {
        VStack {
            Button("情報アラート") {
                alertType = .informational
                showAlert = true
            }
            Button("警告アラート") {
                alertType = .warning
                showAlert = true
            }
            Button("確認アラート") {
                alertType = .confirmation
                showAlert = true
            }
            Button("エラーアラート") {
                alertType = .error
                showAlert = true
            }
        }
        .alert(isPresented: $showAlert) {
            switch alertType {
            case .informational:
                return Alert(
                    title: Text("情報"),
                    message: Text("これは情報アラートです。"),
                    dismissButton: .default(Text("OK"))
                )
            case .warning:
                return Alert(
                    title: Text("警告"),
                    message: Text("これは警告アラートです。"),
                    dismissButton: .default(Text("了解"))
                )
            case .confirmation:
                return Alert(
                    title: Text("確認"),
                    message: Text("この操作を実行してもよろしいですか?"),
                    primaryButton: .destructive(Text("実行"), action: {
                        // 処理をここに記述
                    }),
                    secondaryButton: .cancel(Text("キャンセル"))
                )
            case .error:
                return Alert(
                    title: Text("エラー"),
                    message: Text("エラーが発生しました。"),
                    dismissButton: .default(Text("閉じる"))
                )
            }
        }
    }
}

このコードでは、一つの".alert"文の中でswitch文を利用して異なるアラートを出し分けています。これによって複数のアラートがしっかり動作する状態にでき、新しいアラートタイプを追加する時もenumに新しいケースを追加するだけでできて簡潔に実装できますね。

Voicyテックブログ

Discussion