【问题标题】:SwiftUI access global state from ViewModelSwiftUI 从 ViewModel 访问全局状态
【发布时间】:2020-05-24 13:44:27
【问题描述】:

我正在试验 SwiftUI,但很难为我的应用找出合适的架构。

我想要实现的目标很简单。我想要一个初始屏幕,根据当前的身份验证状态显示给定用户的注册屏幕或主屏幕。一旦进行了身份验证,我无法弄清楚如何使初始屏幕从注册屏幕中获取更改。以下是一些相关代码:

struct InitialView: View {
    @EnvironmentObject var viewModel: InitialViewModel

    var body: some View {
        VStack {
            if viewModel.auth.identity != nil {
                NewHomeView()
            } else {
                SignInView()
            }
        }
    }
}

在登录视图中,我有一个常用的登录表单,当用户按下登录按钮时,我想向后端发送登录请求并记住生成的令牌。


class SignInViewModel: ObservableObject {
    private let auth: AuthStore
    private let signInApi: SignInApi
    private var cancellableSet: Set<AnyCancellable>

    // Input
    @Published var name: String = ""
    @Published var password: String = ""
    @Published var showSignUp: Bool = false

    // Output
    @Published var authSuccess: Bool = false

    init(auth: AuthStore, signInApi: SignInApi) {
        self.auth = auth
        self.signInApi = signInApi
        self.cancellableSet = Set<AnyCancellable>()
    }

    func signIn() {
        signInApi.signIn(email: name, password: password).sink(receiveCompletion: { _ in }) { response in
            self.auth.identity = Identity(
                person: Person(id: 1, name: "user", sex: nil, birthday: nil),
                token: response.token
            )
            self.authSuccess = true
        }.store(in: &cancellableSet)
    }
}

但是,这不起作用。即使在单击登录按钮后,初始视图也不会更新。请注意,我将相同的 AuthStore 传递给两个视图模型。

  let auth = AuthStore()

        // Create the SwiftUI view that provides the window contents.
        let contentView = InitialView()
            .environmentObject(InitialViewModel(auth: auth))
            .environmentObject(SignInViewModel(auth: auth, signInApi: SignInApi()))

其中 AuthStore 被定义为


class AuthStore: ObservableObject {
    @Published var identity: Identity? = nil
}

理想情况下,我希望能够 1) 让每个视图与自己的 VM 配对 2) 让每个 VM 通过 @EnvironmentObject 访问全局状态。但是,@EnvironmentObject 似乎仅限于视图?如果是这样,我如何从需要它的每个 VM 中访问全局身份验证状态?

【问题讨论】:

  • 你尝试过这个教程的一个小项目吗? hackingwithswift.com/quick-start/swiftui/…顺便有两个观点……正是你想学的
  • 嘿,我在询问之前查看了那个链接。我没有使用它,因为它基本上迫使我停止使用 MVVM 并在视图内进行所有状态管理。在您发布的链接的语言中,我希望有两个视图模型可以访问相同的 UserSettings(理想情况下从 SceneDelegate 中注入一次)。这可能吗?
  • 查看答案下方的讨论,您必须检查@Published 包装器的用途,以及它是如何工作的stackoverflow.com/questions/60119057/…
  • 另一个问题,哪个环境对象可以在你的 contentView 中访问???
  • 我在这里回答了类似的问题:stackoverflow.com/questions/60134061/…

标签: ios swift mvvm swiftui combine


【解决方案1】:

根据提供的代码,我将其更改如下

struct InitialView: View {
    @EnvironmentObject var viewModel: InitialViewModel
    @EnvironmentObject var signIn: SignInViewModel

    var body: some View {
        VStack {
            if signIn.authSuccess {
                NewHomeView()
            } else {
                SignInView()
            }
        }
    }
}

以及将发布者重定向到主线程

    signInApi.signIn(email: name, password: password)
        .receive(on: RunLoop.main) // << following update should be on main thread
        .sink(receiveCompletion: { _ in }) { response in
        self.auth.identity = Identity(
            person: Person(id: 1, name: "user", sex: nil, birthday: nil),
            token: response.token
        )
        self.authSuccess = true
    }.store(in: &cancellableSet)

【讨论】:

  • 嘿,谢谢。我会试试看。澄清一点:初始视图的视图模型只有 auth,没有 authSuccess。
  • 他的示例中缺少的部分是 SignInViewModel 和 InitialViewModel 之间的一些链接。至少我没有看到...
  • 更新:不,由于某种原因,当我更新 auth.identity 即使它是@Published 等时,它仍然没有注册。我有一种感觉,这可能是因为嵌套的 observableobjects 的变化没有向上传播。我尝试在 InitialViewModel 中执行类似 self.isAuthenticated = self.auth.$identity.map { $0 != nil } 的操作,但我无法正确输入类型。在 Rx 中,这只是一个可观察的,但 Combine 在类型系统中对计算进行编码,它很快就变得一团糟.. :( \
  • @user3441734 我希望链接是我传递给两个 ViewModel(InitialViewModel 和 SignInViewModel)的共享 AuthStore。
  • 他写道“请注意,我将相同的 AuthStore 传递给两个视图模型”...... ????从他的例子很难理解他的业务逻辑
【解决方案2】:

由于您没有提供 InitialViewModel 详细信息,因此在这里冒险猜测。

您没有在SignInViewModel 中观察到auth,也没有发布它来触发视图更新。

我猜这与 InitialViewModel 有同样的问题。

class SignInViewModel: ObservableObject {
    private let auth: AuthStore
    private let signInApi: SignInApi

对于问题 2):你不能。 (据我所知)

对问题1):可以。但代价是什么?有更好的选择吗?

如果需要,我可以详细说明。

【讨论】:

    猜你喜欢
    • 2020-12-18
    • 1970-01-01
    • 2021-12-15
    • 1970-01-01
    • 1970-01-01
    • 2017-05-29
    • 2018-03-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多