Reputation: 2550
How can I use @AppStorage
for a string map in a SwiftUI app?
This is what I want to do:
@AppStorage("ratings") var ratings: [String: Double] = []
But this gives me the error message “No exact matches in call to initializer”. When looking at the documentation, it looks like only a few data types are supported. It is possible to encode it as Data
?
Upvotes: 7
Views: 6257
Reputation: 45
You can use this array extension to use structs in AppStorage, just add this to your code above ContentView:
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
}
}
And then you can create a struct for your data type:
struct Rating: Hashable, Codable {
var howManyStars: Int
var notes: String
}
Then make your AppStorage array like this:
@AppStorage("rating") var ratings: [Rating] = []
I hope this helps.
Upvotes: 3
Reputation: 28539
Looking at the documentation for @AppStorage
the only values that you can currently store using this property wrapper are
Int
Double
String
Bool
URL
Data
And their optional counterparts. You can also store values that conform to RawRepresentable
, like enums that conform to Int
or String
.
If you want to store a dictionary using this method then you would have to convert it to data and store it that way.
@AppStorage("ratings")
var ratings: Data = Data() // we need to initialize it with something
Then we can save to it using
let data = ["Hello": 5.0]
guard let ratings = try? JSONEncoder().encode(data) else { return }
self.ratings = ratings
And if we want to retrieve it we can do the following:
guard let decodedRatings = try? JSONDecoder().decode([String:Double].self, from: ratings) else { return }
print(decodedRatings)
Otherwise you will have to use UserDefaults directly, you can always use onChange
and State to manage it. See this example of how to use onChange
. You may need to create a custom init for your view so as to populate the State the value from UserDefaults.
Though you could write your own property wrapper, this article by John Sundell explains in detail how to do it.
Upvotes: 22