Issue #949
AppStorage and SceneStorage accepts RawRepresentable where value is Int or String.
Creates a property that can read and write to a string user default, transforming that to RawRepresentable data type.
init(
wrappedValue: Value,
_ key: String,
store: UserDefaults? = nil
) where Value : RawRepresentable, Value.RawValue == String
One clever thing (that does not work) is to use a custom Codable type that conforms to RawRepresentable, like below
public protocol RawCodable: Codable, RawRepresentable {}
public extension RawCodable {
init?(rawValue: String) {
guard
let data = rawValue.data(using: .utf8),
let result = try? JSONDecoder().decode(Self.self, from: data)
else {
return nil
}
self = result
}
var rawValue: String {
guard
let data = try? JSONEncoder().encode(self),
let result = String(data: data, encoding: .utf8)
else {
return ""
}
return result
}
}
struct Book: RawCodable {}
struct BooksView: View {
@AppStorage("favorites") var favorite: Book?
var body: some View {
List {
}
}
}
The above will cause infinite loop as JSONEncoder
will use rawValue
to encode, see Codable.swift
extension RawRepresentable<String> where Self: Encodable {
/// Encodes this value into the given encoder, when the type's `RawValue`
/// is `String`.
///
/// This function throws an error if any values are invalid for the given
/// encoder's format.
///
/// - Parameter encoder: The encoder to write data to.
public func encode(to encoder: any Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(self.rawValue)
}
}
To avoid this, we can implement our own encode(to:)
to avoid infinite loop.
Another way is to introduce a container struct that conforms to RawRepresentable
public struct RawCodable<Value: Codable>: RawRepresentable {
public var wrappedValue: Value
public init(
wrappedValue: Value
) {
self.wrappedValue = wrappedValue
}
public init?(rawValue: String) {
guard
let data = rawValue.data(using: .utf8),
let value = try? JSONDecoder().decode(Value.self, from: data)
else {
return nil
}
self.wrappedValue = value
}
public var rawValue: String {
guard
let data = try? JSONEncoder().encode(wrappedValue),
let string = String(data: data, encoding: .utf8)
else {
return ""
}
return string
}
}