Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Updating @Published variable of an ObservableObject inside child view

I have a published variable isLoggedIn inside a ObservableObject class as follows:

import Combine

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

I want to update this variable to true in a particular view (LoginView). This variable determines what view I show the user depending if the user has logged in or not:

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

Because userAuth.isLoggedIn is false (I haven't logged in) AccountView is displayed.

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

Whenever the user presses the button the LoginView Modal is shown:

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

In the LoginView there is a button, the logic I want is for the user to press the button, login() gets called and inside that function userAuth.isLoggedIn is set to true. What would be the best way to implement this ?

I've tried to directly change the value and I get an error along the lines of:

Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive
like image 958
39fredy Avatar asked Sep 05 '19 03:09

39fredy


2 Answers

Try embedding your code in DispatchQueue.main.async like this:

func login(){
    DispatchQueue.main.async {
        //update UserAuth().isLoggedIn to TRUE
    }
}
like image 87
J. Edgell Avatar answered Nov 04 '22 20:11

J. Edgell


One possibility would be to insert the UserAuth object from the ContentView into the subviews as an EnvironmentObject.

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

Now userAuth is accessible in AccountView and all its subviews (e.g. 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
        }
    }
}

It may be necessary to insert the EnvironmentObject into the LoginView manually. You can do this by accessing it in the AccountView and inserting it in the 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()
        }
    }
}

Further Reading
WWDC 2019 Video | Hacking with Swift Example

like image 23
iComputerfreak Avatar answered Nov 04 '22 22:11

iComputerfreak