Reputation: 4330
@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
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
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