Empresas
Empregos
  • Sobre nós
  • Soluções
    • Publicação de vagas
      Publique sua vaga e receba candidatos qualificados em 48h.
    • Avaliações de candidatos
      Mais de 500 testes técnicos e psicológicos, mais anti-fraude.
    • Headhunting
      Busca executiva personalizada do início ao fim.
    • Folha de Pagamento + EOR
      Dispersão de folha e EOR em mais de 15 países da LATAM.
  • Preços
  • Empregos

0

162
Visualizações
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 Respostas
Responde à pergunta

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 Relatório
Responde à pergunta
Encontrar trabalhos remotos

Descubra a nova forma de encontrar um emprego!

melhores empregos
Principais categorias de trabalho
Empresas
Postar vaga Preços Comercial
Jurídico
Termos e Condições Política de privacidade
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomende algumas ofertas para mim
Preciso de ajuda