问题
I'm trying to build an app using SwiftUI and an MVVM architecture. I'd like to have my View present an alert whenever its ViewModel deems it necessary—say, when it has a new result of some sort available from the Model. So suppose whenever the VM detects a new result it sets its status accordingly:
The ViewModel:
enum Status {
case idle
case computing
case newResultAvailable
}
class MyViewModel: ObservableObject {
@Published var status = Status.idle
...
}
The View:
struct ContentView: View {
@ObservedObject var vm = MyViewModel()
@State private var announcingResult = false {
didSet {
// reset VM status when alert is dismissed
if announcingResult == false {
vm.status = .idle
}
}
}
var body: some View {
Text("Hello")
.alert(isPresented: $announcingResult) {
Alert(title: Text("There's a new result!"),
message: nil,
dismissButton: .default(Text("OK")))
}
}
}
Apple has designed the .alert() modifier to take a binding as its first argument, so that the alert is displayed whenever the bound property becomes true. Then, when the alert is dismissed, the bound property is automatically set to false.
My question is:
How can I have the alert appear whenever the VM's status becomes .newResultAvailable? It seems to me that that's how proper MVVM should function, and it feels very much in the spirit of all the SwiftUI WWDC demos, but I can't find a way…
回答1:
Here is possible approach (tested & works with Xcode 11.3+)
struct ContentView: View {
@ObservedObject var vm = MyViewModel()
var body: some View {
let announcingResult = Binding<Bool>(
get: { self.vm.status == .newResultAvailable },
set: { _ in self.vm.status = .idle }
)
return Text("Hello")
.alert(isPresented: announcingResult) {
Alert(title: Text("There's a new result!"),
message: nil,
dismissButton: .default(Text("OK")))
}
}
}
also sometimes the following notation can be preferable
var body: some View {
Text("Hello")
.alert(isPresented: Binding<Bool>(
get: { self.vm.status == .newResultAvailable },
set: { _ in self.vm.status = .idle }
)) {
Alert(title: Text("There's a new result!"),
message: nil,
dismissButton: .default(Text("OK")))
}
}
回答2:
I have created a helper class
class AlertProvider {
struct Alert {
var title: String
let message: String
let primaryButtomText: String
let primaryButtonAction: () -> Void
let secondaryButtonText: String
}
@Published var shouldShowAlert = false
var alert: Alert? = nil { didSet { shouldShowAlert = alert != nil } }
}
In the VM it can be used like this
var alertProvider = AlertProvider()
func showAlert() {
alertProvider.alert = AlertProvider.Alert(
title: "The Locatoin Services are disabled",
message: "Do you want to turn location on?",
primaryButtomText: "Go To Settings",
primaryButtonAction: { [weak self] in
self?.appSettingsHandler.openAppSettings()
},
secondaryButtonText: "Cancel"
)
}
We can also use an extension for the Alert View class
extension Alert {
init(_ alert: AlertProvider.Alert) {
self.init(title: Text(alert.title),
message: Text(alert.message),
primaryButton: .default(Text(alert.primaryButtomText),
action: alert.primaryButtonAction),
secondaryButton: .cancel(Text(alert.secondaryButtonText)))
}
}
Then the View would use it in the following way
.alert(isPresented: $viewModel.alertProvider.shouldShowAlert ) {
guard let alert = viewModel.alertProvider.alert else { fatalError("Alert not available") }
return Alert(alert)
}
I believe further improvements can be made in this approach
来源:https://stackoverflow.com/questions/60102432/presenting-an-alert-in-swiftui-using-mvvm