问题
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