はじめに
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に書いていないので、スッキリしていてかなり見やすくなっています!
参考になれば幸いです!
最後に
弊社では、経験の有無を問わず採用を行っています。
興味のある方は是非カジュアル面談しましょう!