NEW: My new book Pro SwiftUI is out now – level up your SwiftUI skills today! >>

ObservedObject not updated in View when Published var is changed

Forums > SwiftUI

@Swink  

Attempting to update my main dashboard view once a user makes updates on their settings page. The issue is, the view does not update.

It updates correctly in the database and if I close the app out and reopen it displays the new data correctly. I would like the data to refresh live on the app when updated. I figure I am not using StateObject and ObservedObject correctly.

If I can get some insight, help on the issue that'll be great. I think posting the entire code base would be redundant in solving the issue, for now I'll just post each view and how I'm passing data in between the views. I won't post my model class either, since I don't think the problem lies there (as I'm using an observableObject class with my var's all being Published.

struct UserDashboardView: View {
    @StateObject var vm = DashboardLogic ()
      var body: some View {
            TabView{
                UserDashController(vm: vm, signUpController: signUpController)
                        .tabItem{
                            VStack{
                                Image(systemName: "house.circle")
                                    .font(.title3)
                                Text("Home")
                            }
                        }

                        struct UserDashController: View {
    @ObservedObject var vm: DashboardLogic

    var body: some View {
        if !vm.isUserDataLoading { // << if user data loaded
            NavigationView{
                    HStack{
                        Text(vm.userModel?.name ?? "" ) // << what needs to change when udpated
                            .font(.title3)
                    }

                    ProfileBio(userBio: vm.userModel?.userBio ?? "") //changes when updated
                        .padding(.top, -25)
            }
            .fullScreenCover(isPresented: $presentSettingsPage){
                PersonalSettingsView(vm: vm)  << where I update the data in this view
            }

        }

    } 
}
struct PersonalSettingsView: View {

    @ObservedObject var vm: DashboardLogic  //call to viewModel
    var body: some View {
        NavigationView{
                    UpdatePersonalSettingsHStack(vm: vm, name: 
                        .constant(vm.userModel?.name ?? "Name not found"))

                    HStack{
                        Image(systemName: "person.crop.rectangle")
                            .foregroundColor(Color("ButtonTwo"))
                        TextField(vm.userModel?.userBio ?? "UserBio", text: $userBio).submitLabel(.done)
                            .onSubmit{
                                if (vm.userModel?.userBio != userBio){
                                    FirebaseManager.shared.firestore.collection("users").document(FirebaseManager.shared.auth.currentUser!.uid).updateData(["userBio": userBio])
                                    showSuccessAlertForName.toggle()
                                    print("user Bio updated")
                                }
                            }
                            .alert(isPresented: $showSuccessAlertForName, content: {
                                Alert(title: Text("Bio Entered"),
                                       message: Text(""), dismissButton:
                                            .default(Text("Close")))
                            })
                            .padding(.trailing, 20)
                    }

                    class DashboardLogic: ObservableObject, Identifiable {
    @Published var userModel: UserModel?
    @Published var privateUserModel: privateUserModel?

    init(){
        self.fetchCurrentUser()
    }

    func fetchCurrentUser () {
        guard let uid = FirebaseManager.shared.auth.currentUser?.uid else {
            return
        }

        FirebaseManager.shared.firestore
            .collection("users").document(uid)
            .getDocument { snapshot, error in
                if let error = error {
                    print ("failed to fetch user \(error)")
                    return
                }

            guard let data = snapshot?.data() else {
                UserDefaults.standard.set(false, forKey: "signedIn") // << update appStorage if no user
                print ("no data found for user")
                return
            }
                DispatchQueue.main.async {
                    self.userModel = .init(data: data)
                    self.isUserDataLoading = false
                }

        }

    }
}

struct UserModel: Identifiable, Hashable {
    var id: String = UUID().uuidString

    var uid, name, gender, height, weight,userBio, agenda, profilePictureURL: String

    init(data: [String: Any]){
        self.uid = data["uid"] as? String ?? "Unavailable"
        self.name = data["name"] as? String ?? "Unavailable"
        self.gender = data["gender"] as? String ?? "Unavailable"
        self.height = data["height"] as? String ?? "Unavailable"
        self.weight = data["weight"] as? String ?? "Unavailable"
        self.userBio = data["userBio"] as? String ?? "No Bio entered"
        self.agenda = data["agenda"] as? String ?? "Unavailable"
        self.profilePictureURL = data ["profilePicture"] as? String ?? "Unavailable"
    }
}

   

@Swink  

I apologize, first time posting to the forum. I updated my post. Thank you.

   

Please delete your apology. We have ALL BEEN THERE!

It's just difficult to help when the code format is blorked like it was.

   

There's a lot to unpack, so I might not get right to the answer. But some of these comments should help you better organize your code base.

For example: You have a model, named UserModel that describes a typical user. Nice. I see you also have a few views where you display data. Makes sense.

And you have a ViewModel, named DashboardLogic where (presumably) you grab data from your model, and prep it to display in your views. Also the view model is a place where you put intents. These are the methods you want done to the model when your user pushes buttons, drags sliders, and taps things in your interface.

So here's an example where you might consider adding a computed var to your view model so that you remove business logic from your views.

// In UserDashController--------------
// ..... snip ..........
HStack {
    Text(vm.userModel?.name ?? "" ) // <-- Pull this logic into your view model. 
    // Views just display. Don't make them think!
    }

In your ViewModel, DashboardLogic, you have a userModel. In your view you want to display a username, userModel.name. But what if the (optional) userModel is nil? or the name is nil?
What do you want your UserDashController view to display?
Consider adding a computed var to your ViewModel. Call it displayName. Then in your UserDashController, and elsewhere, when you want to display a user's name you declare the Text() will show the displayName.

// In your view model....
public var displayName: String {
    userModel?.name ?? "unknown"  // <--This is calculated when needed.
}

// In your view .......
HStack {
    Text( vm.displayName ) // <-- Declare what you want to display
}

   

...continued....

Now that this is a little cleaner. Let's look at the HStack and figure out why it's not updating when the name changes.

In your view, UserDashController, you have a reference to an @ObservedObject named vm, of type DashboardLogic.

This tells me that whenever the published objects in DashboardLogic change it will broadcast a notification to all views that are observing it. As a reminder, the vm is declared as an @StateObject in your UserDashboardView.

Also remember, vm is a reference type. This means you pass a pointer of the object to children views, including UserDashController so that all views are looking at the same DashboardLogic instance.

But this is where I run out of steam. I see your DashboardLogic class is an ObservableObject. But I don't see where you update the userModel @Published var. If you're updating the privateUserModel, and publishing that to Firebase, this might explain why you see the updated data when you reload. But if you're not updating the userModel, I'm not sure those changes get published to the views in your application.

Just a guess, because I think some crucial code is missing from your snips.

(Also, why? why do you declare structs and classes 1/2 across the screen instead of on the leading edge?)

   

I tried to copy your code into my XCode so I could take a better look, but there are a bunch of missing closing braces and things that can not be found in scope. So, I can't really tell what is supposed to be going on exactly.

But, if I had to guess a reason why the view does not appear to update until after you close and reopen the app, it might be that the changes to your data model may not be happening on the mainActor thread. So, maybe the view is reloading before all of the data actually loads. So, by the time the data finishes loading, the View thinks that it has already updated itself since the last change, and doesn't need to update again even though new data finished loading later. However, when you restart the app after the data has finished loading, then the view shows all of the data that it should.

   

@Swink  

  • Wow alot to unpack here.

Obelix First off thank you! The fact you took the time and explained your thought process was so valuable. I appreciate it beyond belief!! Will definately refactor my codebase with your tips in mind.

@flyostrich You hero thank you! You nailed it, on the main thread I wasn't updating my model. I slapped an objectwillchange in there, linked it to my model class and viola. A week's problem is now solved.

Thank you both for all the help on this again, sincerely appreciate it!

1      

Hacking with Swift is sponsored by Play

SPONSORED Play is the first native iOS design tool created for designers and engineers. You can install Play for iOS and iPad today and sign up to check out the Beta of our macOS app with SwiftUI code export. We're also hiring engineers!

Click to learn more about Play!

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.