🐰
SwiftUIのAlertを少し使いやすくする
最近SwiftUIとMVVMでアプリを作成しているのですが、AlertでViewが肥大化してしまい見通しが悪かったので少し改善してみました。
環境
Xcode 13.0 (13A233)
Swift 5.5
通常の実装例
通常の実装例がこちらになります。
(実際にはMVVM想定なのでCombineを使っていますが、今回は簡略化のためにある程度省略しています)
struct ContentView: View {
@ObservedObject var viewModel: ContentViewModel
var body: some View {
Button("アラートを表示する") {
viewModel.isShowingAlert = true
}
.alert("タイトル", isPresented: $viewModel.isShowingAlert) {
Button("OK") {
// do something
}
} message: {
Text("詳細メッセージ")
}
}
}
class ContentViewModel: ObservableObject {
@Published var isShowingAlert: Bool = false
}
この実装だと、画面ごとにAlertを表示するフラグやタイトル、メッセージなどを格納する変数を用意する必要があり、またactionやmessageなどでViewのコードも肥大化しがちになります。
改善した実装例
改善したコードがこちらになります。
ボタンが1つもしくは2つの2パターンを想定して実装しています。
フラグやtitleなどを管理するEntity
Alertを表示するフラグやタイトル、ボタンタップ時の動作を定義したEntityを用意します。
enum AlertType {
case single
case double
}
struct AlertEntity {
var isShowingAlert: Bool = false
var alertType: AlertType = .single
var title: String = ""
var message: String = ""
var buttonAction: () -> Void = {}
mutating func show(alertType: AlertType = .single, title: String, message: String, buttonAction: @escaping () -> Void = {}) {
self.alertType = alertType
self.title = title
self.message = message
self.buttonAction = buttonAction
self.isShowingAlert = true
}
mutating func hide() {
self.isShowingAlert = false
}
}
Alertを共通化したViewModifier
デフォルトのままalertを実装するとViewが肥大化するのでViewModifierで共通化します。
extensionは簡単に適用できるように用意したものなので無くても大丈夫です。
struct CustomAlertView: ViewModifier {
@Binding var alertEntity: AlertEntity
func body(content: Content) -> some View {
content
.alert(isPresented: $alertEntity.isShowingAlert) {
switch alertEntity.alertType {
case .single:
return Alert(title: Text(alertEntity.title),
message: Text(alertEntity.message),
dismissButton: .default(Text("閉じる"), action: {
alertEntity.buttonAction()
}))
case .double:
return Alert(title: Text(alertEntity.title),
message: Text(alertEntity.message),
primaryButton: .default(Text("いいえ")),
secondaryButton: .destructive(Text("はい"), action: {
alertEntity.buttonAction()
}))
}
}
}
}
extension View {
func customAlert(for alertEntity: Binding<AlertEntity>) -> some View {
modifier(CustomAlertView(alertEntity: alertEntity))
}
}
使用例
ViewModelに先程記載したEntityを用意し、任意のViewに独自に適宜したCustomAlertViewを適用させます。
その際にViewModelで定義したEntityをBindingさせておきます。
あとはAlertを呼び出したいところでalertEntityのshowメソッドを発火させればOKです。
struct ContentView: View {
@ObservedObject var viewModel: ContentViewModel
var body: some View {
Button("アラートを表示する") {
viewModel.alertEntity.show(title: "警告", message: "要素が空です。")
}.customAlert(for: $viewModel.alertEntity)
}
}
class ContentViewModel: ObservableObject {
@Published var alertEntity: AlertEntity = AlertEntity()
}
参考
Discussion