【问题标题】:Presenting an Alert in SwiftUI using MVVM使用 MVVM 在 SwiftUI 中显示警报
【发布时间】:2020-02-06 19:45:29
【问题描述】:

我正在尝试使用 SwiftUI 和 MVVM 架构构建应用程序。我想让我的 View 在它的 ViewModel 认为有必要时显示一个警报——比如,当它有一个来自 Model 的某种新结果时。因此,假设每当 VM 检测到新结果时,它都会相应地设置其 status

ViewModel:

enum Status {
    case idle
    case computing
    case newResultAvailable
}

class MyViewModel: ObservableObject {

    @Published var status = Status.idle

    ...
}

观点:

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 设计了 ​​.alert() 修饰符以将绑定作为其第一个参数,以便在绑定属性变为 true 时显示警报。然后,当警报解除时,绑定属性会自动设置为false

我的问题是: 每当 VM 的 status 变为 .newResultAvailable 时,如何让警报出现?在我看来,这就是正确的 MVVM 应该如何发挥作用,并且感觉非常符合所有 SwiftUI WWDC 演示的精神,但我找不到方法……

【问题讨论】:

  • 是否有任何文档参考或解释说明为什么这不起作用?我似乎无法理解这背后的原因。
  • @Rikh 只要将announcingResult 设置为true,它就可以工作。但我的目标是在vm.status 变为某个值时发出警报……而上面的代码没有任何规定可以做到这一点。这就是为什么@asperi 在下面的回答是必要的。
  • 啊,我看错了你上面的例子。道歉。我发现在视图模型中直接使用@Published 值来控制警报也不会导致它出现在View 中。所以一直在寻找答案!

标签: ios swift mvvm swiftui alert


【解决方案1】:

这是可能的方法(经过测试并适用于 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")))
            }
    }
}

有时以下符号可能更可取

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")))
        }
}

【讨论】:

  • 谢谢你,@Asperi!这看起来棒极了。我会试一试。我没有想到我可能必须进行手动绑定……到目前为止我从来没有这样做过,但这似乎是一个优雅的解决方案。如果有一个可以避免它们,我会很高兴;如果没有,我认为这会起作用!
  • 正如@user12208004 指出的那样,我对绑定的覆盖在 Xcode 11.5 中不再起作用。这种行为似乎被打破了。
  • 我没有 Xcode 11.5。使用 Xcode 12 重新测试 - 工作正常。确保在主队列上修改视图模型发布的属性。
  • 我更新到 Xcode 11.6,它也可以正常工作。谢谢@Asperi
【解决方案2】:

我创建了一个辅助类

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 } }
}

在VM中可以这样使用

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"
    )
}

我们还可以使用警报视图类的扩展

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)))
    }
}

那么View会以如下方式使用它

.alert(isPresented: $viewModel.alertProvider.shouldShowAlert ) {
        guard let alert = viewModel.alertProvider.alert else { fatalError("Alert not available") }

        return Alert(alert)
}

我相信这种方法可以进一步改进

【讨论】:

  • 您好,感谢您的回答。您是否不需要将来自 AlertProvider 类 (shouldShowAlert) 的更改从 VM 内部发送到您的视图,然后才能正常工作,还是我遗漏了什么?
猜你喜欢
  • 2021-04-30
  • 2021-08-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-07
  • 1970-01-01
  • 2020-03-22
相关资源
最近更新 更多