JohnSF
JohnSF

Reputation: 4330

@Published ObservedObjects SwiftUI Updates not Happening

@Published ObservedObjects SwiftUI Updates not Happening

I have created a very basic ObservableObject app and some bindings are updated correctly and some are not. I must be missing something simple. Two views only - the start view and a second view. I only use Text, Button and Toggle - just to test this concept.

I want to store startup property values in UserDefaults. That part seems to work fine. When I make changes on the second page, they are updated on that page, and the UserDefaults are correctly written. However, on returning to the start view, the Published bindings are not updated. Interestingly, the UserDefaults are updated.

The main view simply displays the values of the bindings and user defaults.

The start view:

import SwiftUI

struct ContentView: View {
    @State private var showUtilities = false
    @ObservedObject var userDefaultManager = UserDefaultsManager()

    var body: some View {
        NavigationView {
            VStack{
                Group { //group 1
                    Text("Published userDefaultManager.name:")
                    Text("\(UserUtilities().makeSubString(stringIn: self.userDefaultManager.name, count: 24))")
                        .padding(.bottom, 30)
                        .lineLimit(0)

                    Text("UserDefaults.standard.value:")
                    Text("\(UserUtilities().makeSubString(stringIn: UserDefaults.standard.value(forKey: "name") as! String, count: 24))")
                    .padding(.bottom, 30)
                    .lineLimit(0)

                    Text("Published userDefaultsManager.enableBiometrics:")
                    Text(String(self.userDefaultManager.enableBiometrics))
                        .padding(.bottom, 30)
                        .font(.headline)
                } //group 1

                Group { //group 2
                    Text("UserDefaults.standard.bool:")
                    Text(String(UserDefaults.standard.bool(forKey: AppDelegate.eb)))
                        .padding(.bottom, 30)
                        .font(.headline)
                    Button(action: {
                        self.showUtilities.toggle()
                    }) {
                        Text("Show Utilities")
                    }
                    .frame(width: 200)
                    .padding()
                    .font(.headline)
                }//group 2

            }//vstack
            .navigationBarTitle("Read the Values")
            .sheet(isPresented: $showUtilities) {
                UserUtilities()
            }
        }
    }
}

The second view:

import SwiftUI
import Combine

struct UserUtilities: View {
    @ObservedObject var userDefaultManager = UserDefaultsManager()

    var body: some View {
        NavigationView {
            VStack {
                Toggle(isOn: self.$userDefaultManager.enableBiometrics) {
                    Text("Enable Biometric Login")
                }
                .padding(EdgeInsets(top: 50, leading: 50, bottom: 30, trailing: 50))

                //this does not update
                Text("Published name is \(UserUtilities().makeSubString(stringIn: self.userDefaultManager.name, count: 24))")
                    .padding(EdgeInsets(top: 0, leading: 0, bottom: 30, trailing: 0))

                Text("UserDefaults name is \(UserUtilities().makeSubString(stringIn: UserDefaults.standard.value(forKey: "name") as! String, count: 24))")
                    .padding(EdgeInsets(top: 0, leading: 0, bottom: 30, trailing: 0))

                \\this does not update
                Text("Published enableBiometrics is "  + String(self.userDefaultManager.enableBiometrics) )

                Text("UserDefaults is " + String(UserDefaults.standard.bool(forKey: AppDelegate.eb)) )
                    .padding(.bottom, 20)

                Button(action: {
                    self.userDefaultManager.name = UUID().uuidString
                }) {
                    Text("Change the Published name")
                }
                .padding()
                .font(.headline)

            }
            .navigationBarTitle("User Utilities", displayMode: .inline)
        }
    }//body

    func makeSubString(stringIn: String, count: Int) -> String {
        if stringIn.count > count {
            let modString = stringIn.dropFirst(count)
            let returnString = String(modString)
            return returnString
        }
        return "can't get substring"
    }
}

My UserDefault Manager:

import SwiftUI
import Combine

class UserDefaultsManager: ObservableObject {
    @Published var name = UserDefaults.standard.value(forKey: "name") as! String {
           didSet {
               UserDefaults.standard.set(self.name, forKey: "name")
           }
    }

    @Published var enableBiometrics: Bool = UserDefaults.standard.bool(forKey: AppDelegate.eb) {
        didSet {
            UserDefaults.standard.set(self.enableBiometrics, forKey: AppDelegate.eb)
        }
    }
}

And for completeness - in the AppDelegate:

static let eb = "enablebiometrics"
@ObservedObject var userDefaultManager = UserDefaultsManager()

In didFinishLaunchingWithOptions:

    if UserDefaults.standard.value(forKey: AppDelegate.eb) == nil {
        UserDefaults.standard.set(false, forKey: AppDelegate.eb)
    }

    userDefaultManager.enableBiometrics = UserDefaults.standard.bool(forKey: AppDelegate.eb)

    if UserDefaults.standard.value(forKey: "name") == nil {
        UserDefaults.standard.set("set in AppDelegate", forKey: "name")
    }

    userDefaultManager.name = UserDefaults.standard.value(forKey: "name") as! String

Any guidance would be appreciated. Xcode 11.3 (11C29)

Upvotes: 2

Views: 737

Answers (2)

Ivan C Myrvold
Ivan C Myrvold

Reputation: 840

In UserUtilities, change @ObservedObject var userDefaultManager = UserDefaultsManager() to @Binding var userDefaultManager: UserDefaultsManager, and pass the userDefaultManager variable in ContentView as a parameter to UserUtilities.

Upvotes: 2

Marc T.
Marc T.

Reputation: 5340

You are using 2 Instances of your UserDefaultsManager. Instantiate it only one and pass it to the second view or add it to the environment (@EnvironmentObject) to access it in all the views.

A good example how to to this you can find at https://www.hackingwithswift.com/quick-start/swiftui/how-to-use-environmentobject-to-share-data-between-views.

Upvotes: 4

Related Questions