I want to use AppStorage for a custom struct I am using
struct Activities {
var name: String
var age: Int
}
struct ContentView: View {
@AppStorage("key") private var activities: Activities = .init(name: "Albert", age: 42)
var body: some View {
VStack {
TextField("Activity Name", text: $activities.name)
}
}
}
The above code generates a compiler warning, recommending I add RawRepresentable conformance. So I've added it like this:
extension Activities: RawRepresentable {
public init?(rawValue: String) {
guard let data = rawValue.data(using: .utf8) else {
return nil
}
do {
let result = try JSONDecoder().decode(Activities.self, from: data)
self = result
}
catch {
print(error)
return nil
}
}
var rawValue: String {
guard let data = try? JSONEncoder().encode(self),
let result = String(data: data, encoding: .utf8) else {
return "{}"
}
return result
}
}
This leads to a stack overflow because calling encode from rawValue calls rawValue. :-( I overcame this by declaring Codable conformance and overriding the default Encodable implementation:
extension Activities: Codable {
enum CodingKeys: String, CodingKey {
case name
case age
}
func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode(age, forKey: .age)
}
}
This solves the stack overflow, but now init?(rawValue: String) is failing and I'm not sure why. When I set a breakpoint in my catch block I see the following:
(lldb) po error
▿ DecodingError
▿ typeMismatch : 2 elements
- .0 : Swift.String
▿ .1 : Context
- codingPath : 0 elements
- debugDescription : "Expected to decode String but found a dictionary instead."
- underlyingError : nil
(lldb) po rawValue
{"name":"Albert2","age":42}
(lldb) po data
▿ 27 bytes
- count : 27
▿ bytes : 27 elements
- 0 : 123
- 1 : 34
- 2 : 110
- 3 : 97
- 4 : 109
- 5 : 101
- 6 : 34
- 7 : 58
- 8 : 34
(truncated to save space for posting :-)