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



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

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

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


:::note warn
複数Alertを実装したのに、1つしか表示されない例
:::
ちょうどいい記事を見つけたので、下記の記事も読んでみてください！

https://qiita.com/kyohei-jambo/items/b64a0f0b07fc5ef46adb#

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


# どうやって解決するのか？
**サポートしているiOSのversionによってアプローチは変わります！！**

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

## CustomAlertのモディファイアを作成
```swift
// ここでは、プロジェクトで使用する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の処理を作成
```swift
    // アラートの表示、非表示を分ける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の処理を作成

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

### 参考にした記事

https://zenn.dev/spyc/articles/993fb47a1d42e8


:::note info
プロジェクトが**iOS16**以上のみをサポートしている場合
:::
自分のプロジェクトでは使用していないのでコードはありませんが、下記の記事で解決することができそうです！
簡潔にまとめると、アラート専用のclassを作成し、それをview側でObserveしているようです！

https://www.fuwamaki.com/article/396

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

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

https://jambo-support.com/recruit_engineer/