11
11

SwiftUI iOS version別 Alertで困ったときのTips集

Last updated at Posted at 2024-01-30

はじめに

SwiftUIで、アラートの表示処理を行っているにも関わらず、アラートが実際には表示されないといった不具合が発生していました。
1つのViewで複数の種類のアラートを実装する必要があり、SwiftUIのアラートについて調べる機会があったのでここで共有したいと思います。
なお、今回はプロジェクトのアーキテクチャにそって、MVVMでコードは書いています

SwiftUIの仕様について

まずは、SwiftUIのAlertの仕様について簡単に説明します
結論、alertは各Viewに対して1つしか保持することができません

具体的には、1つのViewに複数のAlertを定義しても以前書いたものは上書きされ、最後に書いたAlertのみ適用されるため、1つしか表示することができないのです。

複数Alertを実装したのに、1つしか表示されない例

ちょうどいい記事を見つけたので、下記の記事も読んでみてください!

この場合、何が困るのか?

  • 異なる種類のAlertを表示したい時、1つしかAlertが適用されない
  • ビルド、ランできてしまうため、開発者がAlertが表示できていないことに気づきにくい

どうやって解決するのか?

サポートしているiOSのversionによってアプローチは変わります!!

プロジェクトがiOS16より下をサポートしている場合

CustomAlertのモディファイアを作成

// ここでは、プロジェクトで使用するAlertの全タイプを記載しています
// ex) 閉じるだけのアラート、閉じた後に何かアクションをさせたい時のアラート、、、
struct CustomAlert: ViewModifier {
    enum AlertType {
        case close(title: String?, message: String?)
        case popBack(title: String?, message: String?, action: (() -> Void)?)
        case someAction(title: String?, message: String?, action: (() -> Void)?)
        case forceUpdate(action: (() -> Void)?)
    }

    @Binding var isPresent: Bool
    let type: AlertType

    func body(content: Content) -> some View {
        content.alert(isPresented: $isPresent) {
            switch type {
            case .close(title: let title, message: let message):
                return Alert(title: Text(title ?? ""),
                             message: Text(message ?? ""),
                             dismissButton: .default(Text(Strings.close.rawValue)))
            case .popBack(title: let title, message: let message, let action):
                return Alert(title: Text(title ?? ""),
                             message: Text(message ?? ""),
                             dismissButton: .default(Text(Strings.close.rawValue), action: {
                    action?()
                }))
            case .someAction(title: let title, message: let message, let action):
                return Alert(title: Text(title ?? ""),
                             message: Text(message ?? ""),
                             primaryButton: .default(Text(Strings.yes.rawValue), action: {
                    action?()
                }),
                             secondaryButton: .default(Text(Strings.no.rawValue)))
            case .forceUpdate(let action):
                return Alert(title: Text(Strings.needUpdateApplicationVersion.rawValue),
                             message: Text(Strings.explanationForUpdate.rawValue),
                             dismissButton: .default(Text(Strings.transitAppStore.rawValue), action: {
                    action?()
                }))
            }
        }
    }

}

ViewModelの処理を作成

    // アラートの表示、非表示を分けるBool値
    // 表示したいアラートのタイプを管理するcurrentAlertType
    @Published var isAlertActivate = false
    @Published var alertMessage = ""
    @Published var alertTitle = ""
    @Published var currentAlertType: CustomAlert.AlertType?

// 閉じるだけのアラート
    func setCloseAlert(title: String, message: String) {
        alertTitle = title
        alertMessage = message
        currentAlertType = .close(title: alertTitle, message: alertMessage)
        isAlertActivate = true
    }

// 閉じるボタン押下後、画面遷移をするView
    func showPopBackAlert(title: String, message: String) {
        currentAlertType = .popBack(title: title, message: message, action: {
            self.onPopBack?()
        })
        isAlertActivate = true
    }

Viewの処理を作成

    @Environment(\.presentationMode) var presentationMode
    VStack {
        適当なコンポーネント
    }
    // Viewの描画が行われると、ViewModelに処理を渡します
    .onAppear {
        viewModel.onPopBack = {
            presentationMode.wrappedValue.dismiss()
        }
    }
    .modifier(CustomAlert(isPresent: $viewModel.isAlertActivate, type: viewModel.currentAlertType ?? .close(title: "予期していないエラー", message: "")))

参考にした記事

プロジェクトがiOS16以上のみをサポートしている場合

自分のプロジェクトでは使用していないのでコードはありませんが、下記の記事で解決することができそうです!
簡潔にまとめると、アラート専用のclassを作成し、それをview側でObserveしているようです!

まとめ

かなり駆け足になりましたが、SwiftUIのAlert問題はどれかを実装すれば対応することができます!
どちらもAlertの出しわけ処理をViewに書いていないので、スッキリしていてかなり見やすくなっています!
参考になれば幸いです!

最後に

弊社では、経験の有無を問わず採用を行っています。
興味のある方は是非カジュアル面談しましょう!

11
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
11