Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save mezhevikin/ae9657348f35b440abd1afb6fe39ff34 to your computer and use it in GitHub Desktop.
Save mezhevikin/ae9657348f35b440abd1afb6fe39ff34 to your computer and use it in GitHub Desktop.
@PublishedAppStorage
// PublishedAppStorage.swift
import Combine
import SwiftUI
@propertyWrapper
struct PublishedAppStorage<Value> {
@UserDefault private var storedValue: Value
private var publisher: Publisher?
private var objectWillChange: ObservableObjectPublisher?
// swiftlint:disable nesting
struct Publisher: Combine.Publisher {
typealias Output = Value
typealias Failure = Never
func receive<S: Subscriber>(subscriber: S) where S.Input == Value, S.Failure == Never {
subject.subscribe(subscriber)
}
fileprivate let subject: CurrentValueSubject<Value, Never>
fileprivate init(_ output: Output) {
self.subject = .init(output)
}
}
// swiftlint:enable nesting
var projectedValue: Publisher {
mutating get {
if let publisher {
return publisher
}
let publisher = Publisher(storedValue)
self.publisher = publisher
return publisher
}
}
@available(*, unavailable, message: "@Published is only available on properties of classes")
var wrappedValue: Value {
get { fatalError() }
set { fatalError() } // swiftlint:disable:this unused_setter_value
}
static subscript<EnclosingSelf: ObservableObject>(
_enclosingInstance object: EnclosingSelf,
wrapped _: ReferenceWritableKeyPath<EnclosingSelf, Value>,
storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, PublishedAppStorage<Value>>
) -> Value {
get { object[keyPath: storageKeyPath].storedValue }
set {
(object.objectWillChange as? ObservableObjectPublisher)?.send()
object[keyPath: storageKeyPath].publisher?.subject.send(newValue)
object[keyPath: storageKeyPath].storedValue = newValue
}
}
init(wrappedValue: Value, _ key: String, store: UserDefaults = .standard) {
self._storedValue = UserDefault(wrappedValue: wrappedValue, key, store: store)
}
}
// UserDefault.swift
import Foundation
@propertyWrapper
struct UserDefault<Value> {
let defaultValue: Value
let key: String
var store: UserDefaults
init(wrappedValue defaultValue: Value, _ key: String, store: UserDefaults = .standard) {
self.defaultValue = defaultValue
self.key = key
self.store = store
}
var wrappedValue: Value {
get { store.object(forKey: key) as? Value ?? defaultValue }
set { store.set(newValue, forKey: key) }
}
}
extension UserDefault where Value: ExpressibleByNilLiteral {
init(_ key: String, store: UserDefaults = .standard) {
self.init(wrappedValue: nil, key, store: store)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment