Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

163
Views
Returning a binding as the projected value of a property wrapper

I need to implement my own version of @AppStorage, as I also want to sync the user defaults to other devices, via iCloud's KeyValue storage.

I need the projectedValue to return a binding so I can use it like this:

struct ContentView: View {  
    @SyncedAppStorage(key: "testKey", defaultValue: true) private var test
    
    var body: some View {
        Toggle(isOn: $test) {
            Text("Test")
        }
    }   
}

In my first attempt the binding property implementation complained I am mutating self:

@propertyWrapper struct SyncedAppStorage<Value>: DynamicProperty {
    let key: String
    var cachedValue: Value
    
    init(key: String, defaultValue: Value) {
        self.key = key
        self.cachedValue = UserDefaults.standard.object(forKey: key) as? Value) ?? defaultValue
    }
    
    var wrappedValue: Value {
        get { getValue() }
        set { setNewValue(newValue) }
    }
    
    var projectedValue: Binding<Value> {
        .init(get: getValue, set: { n in setNewValue(n)} ) // <-- Compile error ...self is immutable
    }
    
    private func getValue() -> Value { cachedValue }
    
    private mutating func setNewValue(_ newValue: Value) {
        UserDefaults.standard.set(newValue, forKey: key)
        NSUbiquitousKeyValueStore.default.set(newValue, forKey: key)
        cachedValue = newValue
    }
}

My next attempt uses an @State property in the property wrapper - which works...

@propertyWrapper struct SyncedAppStorage <Value>: DynamicProperty {
    let key: String
    @State var cachedValue: Value
    
    init(key: String, defaultValue: Value) {
        self.key = key
        self._cachedValue = State(initialValue: (UserDefaults.standard.object(forKey: key) as? Value) ?? defaultValue)
    }
    
    var wrappedValue: Value {
        get { getValue() }
        set { setNewValue(newValue) }
    }
    
    var projectedValue: Binding<Value> {
        .init(get: getValue, set: { n in setNewValue(n)} )
    }
    
    private func getValue() -> Value { cachedValue }
    
    private func setNewValue(_ newValue: Value) {
        UserDefaults.standard.set(newValue, forKey: key)
        cachedValue = newValue
        NSUbiquitousKeyValueStore.default.set(newValue, forKey: key)
    }
}

But is the right way - are there better solutions?

over 4 years ago · Santiago Trujillo
1 answers
Answer question

0

You can use @State here if you want. But out of curiosity I came up with another solution, that uses Combine to work. The issue with your first implementation is the implicit self capturing in your Binding.

@propertyWrapper struct SyncedAppStorage<Value> {

let key: String
private var sinks = Set<AnyCancellable>()

// subject definition
let subject: CurrentValueSubject<Value,Never>

init(key: String, defaultValue: Value) {
    self.key = key
    //Initialize the subject
    subject = CurrentValueSubject(UserDefaults.standard.object(forKey: key) as? Value ?? defaultValue)
    
    //observe changes to subject values
    subject.map{
        UserDefaults.standard.set($0, forKey: key)
        NSUbiquitousKeyValueStore.default.set($0, forKey: key)
    }.sink(receiveValue: {_ in})
        .store(in: &sinks)
}

var wrappedValue: Value{
    get{ subject.value }
    set{ subject.value = newValue}
}

var projectedValue: Binding<Value> {
    return .init(get: { wrappedValue}, set: { subject.value = $0 })
}
}

I removed your cachedValue implementation, as (within the context you provided) i don´t think it is needed.

Kudos to Louis Lac for the link.

over 4 years ago · Santiago Trujillo Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!