SwiftUI从ViewModel访问全局状态

时间:2020-02-08 18:50:29

标签: ios swift mvvm swiftui combine

我正在尝试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)通过@EnvironmentObject使每个VM访问全局状态。但是,似乎@EnvironmentObject只限于视图?如果是这样,我如何从每个需要它的VM中访问全局身份验证状态?

2 个答案:

答案 0 :(得分:0)

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

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)

答案 1 :(得分:0)

在这里提供一个猜测,因为您没有提供InitialViewModel详细信息。

您没有在auth中观察到SignInViewModel,也没有将其发布到老虎视图更新。

我猜这与InitialViewModel是相同的问题。

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

第2个问题):您不能。 (据我所知)

对问题1):可以。但是要花多少钱呢?有更好的选择吗?

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