在子视图中更新 ObservableObject 的 @Published 变量

Posted

技术标签:

【中文标题】在子视图中更新 ObservableObject 的 @Published 变量【英文标题】:Updating @Published variable of an ObservableObject inside child view 【发布时间】:2020-01-07 22:01:03 【问题描述】:

我在 ObservableObject 类中有一个已发布的变量isLoggedIn,如下所示:

import Combine

class UserAuth: ObservableObject
    @Published var isLoggedIn: Bool = false

我想在特定视图 (LoginView) 中将此变量更新为 true。这个变量决定了我向用户显示的视图,具体取决于用户是否登录:

struct ContentView: View 
    @ObservedObject  var userAuth = UserAuth()
    var body: some View 
        Group
            if(userAuth.isLoggedIn)
                MainView()
            else
                AccountView()
            
        
    

因为 userAuth.isLoggedIn 为 false(我还没有登录)AccountView 显示出来了。

帐户视图:

struct AccountView: View 
    @State private var toggleSheet = false
    var body: some View 
        VStack 
            Spacer()
            Button(action: 
                self.toggleSheet.toggle()
            )
                Text("Toggle Modal")
                    .padding()
                    .foregroundColor(Color.white)
                    .background(Color.blue)
                    .cornerRadius(10)
            
            .sheet(isPresented: self.$toggleSheet)
                LoginView()
            
            Spacer()
        
    

每当用户按下按钮时,都会显示 LoginView 模式:

struct LoginView: View 
    var body: some View 
        VStack
            Button(action: 
                return self.login()
            )
                Text("Log in")
                    .padding()
                    .foregroundColor(Color.white)
                    .background(Color.green)
                    .cornerRadius(10)
            
        
    

    func login()
        // update UserAuth().isLoggedIn to TRUE
    

在 LoginView 中有一个按钮,我想要的逻辑是让用户按下按钮,login() 被调用,并且在该函数内部 userAuth.isLoggedIn 设置为 true。实现这一点的最佳方法是什么?

我尝试直接更改该值,但出现以下错误:

Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive

【问题讨论】:

【参考方案1】:

尝试像这样将您的代码嵌入 DispatchQueue.main.async:

func login()
    DispatchQueue.main.async 
        //update UserAuth().isLoggedIn to TRUE
    

【讨论】:

我通过将@ObservedObject var userAuth = UserAuth() 添加到LoginView 并将DispatchQueue.main.async self.userAuth.isLoggedIn = true; 添加到登录函数来做到这一点,但是ContentView 的预期行为不会因为@而呈现MainView() 987654327@ 设置为true ?【参考方案2】:

一种可能性是将UserAuth 对象从ContentView 插入到子视图中作为EnvironmentObject

struct ContentView: View 
    @ObservedObject var userAuth = UserAuth()
    var body: some View 
        Group 
            if userAuth.isLoggedIn 
                MainView()
             else 
                AccountView()
                .environmentObject(userAuth)
            
        
    

现在userAuth 可以在AccountView 及其所有子视图中访问(例如LoginView):

struct LoginView: View 
    // Access to the environment object
    @EnvironmentObject private var userAuth: UserAuth

    var body: some View 
        VStack 
            Button(action: 
                return self.login()
            ) 
                Text("Log in")
                    .padding()
                    .foregroundColor(Color.white)
                    .background(Color.green)
                    .cornerRadius(10)
            
        
    

    func login() 
        // Update the value on the main thread
        DispatchQueue.main.async 
            self.userAuth.isLoggedIn = true
        
    

可能需要手动将 EnvironmentObject 插入LoginView。您可以通过在 AccountView 中访问它并将其插入到 LoginView 中来做到这一点:

struct AccountView: View 
    @State private var toggleSheet = false
    // Access the value, that was inserted in `ContentView`
    @EnvironmentObject private var userAuth: UserAuth

    var body: some View 
        VStack 
            Spacer()
            Button(action: 
                self.toggleSheet.toggle()
            )
                Text("Toggle Modal")
                    .padding()
                    .foregroundColor(Color.white)
                    .background(Color.blue)
                    .cornerRadius(10)
            
            .sheet(isPresented: self.$toggleSheet)
                LoginView()
                // Insert UserAuth object again
                .environmentObject(self.userAuth)
            
            Spacer()
        
    

进一步阅读WWDC 2019 Video | Hacking with Swift Example

【讨论】:

以上是关于在子视图中更新 ObservableObject 的 @Published 变量的主要内容,如果未能解决你的问题,请参考以下文章

Swiftui:子视图没有收到对 observableobject 的更新

UITextField 未更新以更改 ObservableObject (SwiftUI)

如何告诉 SwiftUI 视图绑定到多个嵌套的 ObservableObject

如何使用 ObservableObject 更新 UIViewRepresentable

SwiftUI 视图更新

重新加载数据但 ObservableObject 不工作