Skip to content

Instantly share code, notes, and snippets.

@iamsatar
Forked from JamesSedlacek/Persisting.swift
Created February 4, 2025 09:44
Show Gist options
  • Save iamsatar/fb713b0325e59538721569e499f1a030 to your computer and use it in GitHub Desktop.
Save iamsatar/fb713b0325e59538721569e499f1a030 to your computer and use it in GitHub Desktop.
//
// Persisting.swift
//
// Created by James Sedlacek on 1/30/25.
//
import SwiftUI
/// A property wrapper that manages the persistence of Codable types using UserDefaults.
///
/// The `Persisting` property wrapper provides a convenient way to persist Codable types
/// in UserDefaults by automatically handling the encoding and decoding process.
///
/// # Usage Example
/// ```swift
/// enum TimeFormat: String, Codable, Hashable {
/// case twelveHour = "12h"
/// case twentyFourHour = "24h"
/// }
///
/// struct SettingsView: View {
/// @Persisting("timeFormat") private var format = TimeFormat.twelveHour
///
/// var body: some View {
/// Picker("Time Format", selection: $format) {
/// Text("12 Hour").tag(TimeFormat.twelveHour)
/// Text("24 Hour").tag(TimeFormat.twentyFourHour)
/// }
/// }
/// }
/// ```
@MainActor
@propertyWrapper
public struct Persisting<T: Codable & Hashable>: DynamicProperty {
@AppStorage private var storage: Data
private let defaultValue: T
private let encoder: JSONEncoder = .init()
private let decoder: JSONDecoder = .init()
/// The wrapped value of the Codable type.
public var wrappedValue: T {
get {
guard let decoded = try? decoder.decode(T.self, from: storage) else {
return defaultValue
}
return decoded
}
nonmutating set {
guard let encoded = try? encoder.encode(newValue) else { return }
storage = encoded
}
}
/// A binding to the Codable value stored in `UserDefaults`.
public var projectedValue: Binding<T> {
Binding(
get: { wrappedValue },
set: { wrappedValue = $0 }
)
}
/// Initializes the property wrapper.
///
/// - Parameters:
/// - wrappedValue: The default value if no value is found in UserDefaults.
/// - key: The key used to store the value in UserDefaults.
/// - store: The UserDefaults store to use. Defaults to `.standard`.
public init(
wrappedValue: T,
_ key: String,
store: UserDefaults? = nil
) {
defaultValue = wrappedValue
let initialData = (try? encoder.encode(wrappedValue)) ?? Data()
_storage = .init(wrappedValue: initialData, key, store: store)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment