Skip to content

Instantly share code, notes, and snippets.

@ericlewis
Last active October 15, 2024 21:40
Show Gist options
  • Save ericlewis/37fcd63cd04e03afe8e01f914e52d1fd to your computer and use it in GitHub Desktop.
Save ericlewis/37fcd63cd04e03afe8e01f914e52d1fd to your computer and use it in GitHub Desktop.
Using Codable with AppStorage, the easy way!
import Foundation
import SwiftUI
// MARK: Demo
struct Example: RawRepresentable, Codable {
let id: UUID
}
// This is functionally the same as above, but we have a neater type without creating a new protocol.
typealias RawCodable = RawRepresentable & Codable
struct Example2: RawCodable {
let id: UUID
}
struct ContentView: View {
@AppStorage("1")
var first = [1]
@AppStorage("2")
var second = ["test": 123]
@AppStorage("3")
var third = Example(id: .init())
var body: some View {
Text(first.description)
Text(second.description)
Text(third.id.description)
}
}
// MARK: General Codables
extension RawRepresentable where Self: Codable {
public init?(rawValue: String) {
guard let value: Self = Coders.encode(rawValue) else { return nil }
self = value
}
public var rawValue: String {
Coders.decode(self, default: "{}")
}
}
// MARK: Collections
// Slightly more complex than structs.
extension Collection where Self: RawRepresentable, Self: Codable, Element: Codable {
public var rawValue: String {
Coders.decode(self, default: "[]")
}
}
extension Array: RawRepresentable where Element: Codable {
public init?(rawValue: String) {
guard let value: Self = Coders.encode(rawValue) else { return nil }
self = value
}
}
extension Set: RawRepresentable where Element: Codable {
public init?(rawValue: String) {
guard let value: Self = Coders.encode(rawValue) else { return nil }
self = value
}
}
// MARK: Dictionary
extension Dictionary: RawRepresentable where Key: Codable, Value: Codable {
public init?(rawValue: String) {
guard let value: Self = Coders.encode(rawValue) else { return nil }
self = value
}
public var rawValue: String {
Coders.decode(self, default: "{}")
}
}
// MARK: Private Helpers
fileprivate struct Coders {
private static var _decoder: JSONDecoder { JSONDecoder() }
private static var _encoder: JSONEncoder { JSONEncoder() }
fileprivate static func encode<Value: Codable>(_ value: String) -> Value? {
guard let data = value.data(using: .utf8), let decoded = try? _decoder.decode(Value.self, from: data) else {
return nil
}
return decoded
}
fileprivate static func decode<Value: Codable>(_ value: Value, default: String) -> String {
guard let data = try? _encoder.encode(value), let value = String(data: data, encoding: .utf8) else {
return `default`
}
return value
}
}
@josephlevy222
Copy link

Back in January 2024 I wrote almost identical code using the propertyWrapper as above. Wish I had done a better search then. Now I'm trying to get the key to be the variable name (plus maybe a namespace identifier) but haven't figured out Swift macros enough yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment