Search code examples
swiftswiftuicombineproperty-wrapper

Add @Published behaviour for computed property


I am trying to make a ObservableObject that has properties that wrap a UserDefaults variable.

In order to conform to ObservableObject, I need to wrap the properties with @Published. Unfortunately, I cannot apply that to computed properties, as I use for the UserDefaults values.

How could I make it work? What do I have to do to achieve @Published behaviour?


Solution

  • Updated: With the EnclosingSelf subscript, one can do it!

    Works like a charm!

    import Combine
    import Foundation
    
    class LocalSettings: ObservableObject {
      static var shared = LocalSettings()
    
      @Setting(key: "TabSelection")
      var tabSelection: Int = 0
    }
    
    @propertyWrapper
    struct Setting<T> {
      private let key: String
      private let defaultValue: T
    
      init(wrappedValue value: T, key: String) {
        self.key = key
        self.defaultValue = value
      }
    
      var wrappedValue: T {
        get {
          UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
        }
        set {
          UserDefaults.standard.set(newValue, forKey: key)
        }
      }
    
      public static subscript<EnclosingSelf: ObservableObject>(
        _enclosingInstance object: EnclosingSelf,
        wrapped wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, T>,
        storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Setting<T>>
      ) -> T {
        get {
          return object[keyPath: storageKeyPath].wrappedValue
        }
        set {
          (object.objectWillChange as? ObservableObjectPublisher)?.send()
          UserDefaults.standard.set(newValue, forKey: object[keyPath: storageKeyPath].key)
        }
      }
    }