I think it's better not to pollute Date with a somewhat arbitrary raw representation, everywhere. Instead, you can do something like this—unfortunately, copying and pasting for all necessary overloads, to match the AppStorage API.
@AppStorage.Converter("🗝️") var date = .now
import SwiftUI
public extension AppStorage {
/// A way to use `AppStorage` with more types,
/// via conversion to and from the limited supported types.
typealias Converter<Converted> = StorageConverter<Self, Converted>
}
// MARK: - StorageDynamicProperty
extension AppStorage: StorageDynamicProperty { }
// MARK: - Double
public extension AppStorage<Double>.Converter where Storage == AppStorage<Double> {
init(
wrappedValue: Converted,
_ key: String,
store: UserDefaults? = nil,
fromStorage: @escaping FromStorage,
toStorage: @escaping ToStorage
) {
self.init(
storage: .init(wrappedValue: toStorage(wrappedValue), key, store: store),
fromStorage: fromStorage, toStorage: toStorage
)
}
}
public extension AppStorage<Double>.Converter<Date> {
init(
wrappedValue: Converted,
_ key: String,
store: UserDefaults? = nil
) {
self.init(
wrappedValue: wrappedValue,
key,
store: store,
fromStorage: Date.init(timeIntervalSinceReferenceDate:),
toStorage: \.timeIntervalSinceReferenceDate
)
}
}
import SwiftUI
@propertyWrapper public struct StorageConverter<Storage: StorageDynamicProperty, Converted> {
public typealias ToStorage = (Converted) -> Storage.Value
public typealias FromStorage = (Storage.Value) -> Converted
public var wrappedValue: Converted {
get { fromStorage(storage.wrappedValue) }
nonmutating set { storage.wrappedValue = toStorage(newValue) }
}
public var projectedValue: Binding<Converted> {
.init(
get: { wrappedValue },
set: { wrappedValue = $0 }
)
}
// MARK: internal
init(
storage: Storage,
fromStorage: @escaping FromStorage,
toStorage: @escaping ToStorage
) {
self.storage = storage
self.toStorage = toStorage
self.fromStorage = fromStorage
}
// MARK: private
private let storage: Storage
private let fromStorage: FromStorage
private let toStorage: ToStorage
}
public protocol StorageDynamicProperty<Value>: DynamicProperty {
associatedtype Value
var wrappedValue: Value { get nonmutating set }
}
// MARK: - DynamicProperty
extension StorageConverter: DynamicProperty { }
@AppStorage("storedStartTime") var storedStartTime = Date.now.timeIntervalSinceReferenceDate
var startTime: Date {
set {storedStartTime = newValue.timeIntervalSinceReferenceDate}
get {return Date(timeIntervalSinceReferenceDate: storedStartTime)}
}
Support for Date seems to have been added to @AppStorage in iOS18! Still helpful discussion for other yet unsupported types.
/// Creates a property that can read and write to a date user default.
///
/// - Parameters:
/// - wrappedValue: The default value if a date value is not specified for
/// the given key.
/// - key: The key to read and write the value to in the user defaults
/// store.
/// - store: The user defaults store to read and write to. A value
/// of `nil` will use the user default store from the environment.
@available(iOS 18.0, macOS 15.0, tvOS 18.0, watchOS 11.0, visionOS 2.0, *)
public init(wrappedValue: Value, _ key: String, store: UserDefaults? = nil) where Value == Date