I'm a little confused on how ObservableObject works with UserDefaults and could use some assistance.
I have a data struct that I need to save when a Background Fetch task runs. When the user resumes the app, I want the new data to be displayed.
As this is a struct, I encode it and save as an object, and decode it when reading it. This all works fine.
I created a DataManager ObservableObject class that publishes the data, so that SwiftUI can display it.
The problem is when it runs the background task I'm saving the object to UserDefaults. But in my SwiftUI view, it doesn't pickup the new data when the app resumes. If I kill the app and re-launch it, the new data shows up. How can I get the new data to display on resume?
// save array in background task
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(input) {
UserDefaults.standard.set(encoded, forKey: "myData")
}
// ObservableObject
class DataManager: ObservableObject {
@Published var myData: [MyData] {
didSet {
UserDefaults.standard.myData = myData
objectWillChange.send()
}
init() {
self.myData = UserDefaults.standard.myData
}
}
// Extension
extension UserDefaults {
// @objc dynamic var myDate: [MyData] { // Doesn't work with @objc ? but I think this is needed?
dynamic var myData: [MyData] {
get {
let decoder = JSONDecoder()
if let savedArray = object(forKey: "myData") as? Data {
if let loadedArray = try? decoder.decode([MyData].self, from: savedArray) {
return loadedArray
}
else {
return [MyData]()
}
}
else {
return [MyData]()
}
}
set {
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(newValue) {
set(encoded, forKey: "myData")
}
}
}
// SwiftUI View
struct MySwiftUI: View {
@EnvironmentObject var dataManager: DataManager
var body: some View {
ForEach(dataManager.myData, id: \.number) { item in
// Text(item.number)
}
}
If MyData
is Codable
you can make the array compatible with RawRepresentable<String>
//Allows all Codable Arrays to be saved using AppStorage
extension Array: RawRepresentable where Element: Codable {
public init?(rawValue: String) {
guard let data = rawValue.data(using: .utf8),
let result = try? JSONDecoder().decode([Element].self, from: data)
else {
return nil
}
self = result
}
public var rawValue: String {
guard let data = try? JSONEncoder().encode(self),
let result = String(data: data, encoding: .utf8)
else {
return "[]"
}
return result
}
}
An then just use @AppStorage
, it work straight inside the View
or you can put it in the ObservableObject
class DataManager: ObservableObject {
@AppStorage("myData") var myData: [MyData] = []
}
struct MySwiftUI: View {
@AppStorage("myData") var myData: [MyData] = []
// Your code here.
}
@AppStorage
conforms to DynamicProperty
so it has the capability of telling the body
to reload when it is needed.