Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it correct to expect internal updates of a SwiftUI DynamicProperty property wrapper to trigger a view update?

I'm attempting to create a custom property wrapper supported by SwiftUI, meaning that changes to the corresponding properties values would cause an update to the SwiftUI view. Here is a simplified version of what I have:

@propertyWrapper
public struct Foo: DynamicProperty {
    @ObservedObject var observed: SomeObservedObject

    public var wrappedValue: [SomeValue] {
        return observed.value
    }
}

I see that even if my ObservedObject is contained inside of my custom property wrapper, SwiftUI still catches the changes to SomeObservedObject as long as:

  • My property wrapper is a struct
  • My property wrapper conforms to DynamicProperty

Unfortunately the docs are sparse and I have a hard time telling if this only works out of luck with the current SwiftUI implementation.

The docs of DynamicProperty (within Xcode, not online) seem to indicate that such a property is a property that is changed from the outside causing the view to redraw, but there is no guarantee about what happens when you conform your own types to this protocol.

Can I expect this to continue working in future SwiftUI releases?

like image 418
Pop Flamingo Avatar asked Jan 03 '20 14:01

Pop Flamingo


2 Answers

Ok... here is alternate approach to get similar thing... but as struct only DynamicProperty wrapped around @State (to force view refresh).

It is simple wrapper but gives possibility to incapsulate any custom calculations with following view refresh... and as said using value-only types.

Here is demo (tested with Xcode 11.2 / iOS 13.2):

DynamicProperty as wrapper on @State

Here is code:

import SwiftUI

@propertyWrapper
struct Refreshing<Value> : DynamicProperty {
    let storage: State<Value>

    init(wrappedValue value: Value) {
        self.storage = State<Value>(initialValue: value)
    }
    
    public var wrappedValue: Value {
        get { storage.wrappedValue }
        
        nonmutating set { self.process(newValue) }
    }
    
    public var projectedValue: Binding<Value> {
        storage.projectedValue
    }
    
    private func process(_ value: Value) {
        // do some something here or in background queue
        DispatchQueue.main.async {
            self.storage.wrappedValue = value
        }
    }
    
}


struct TestPropertyWrapper: View {
    
    @Refreshing var counter: Int = 1
    var body: some View {
        VStack {
            Text("Value: \(counter)")
            Divider()
            Button("Increase") {
                self.counter += 1
            }
        }
    }
}
like image 55
Asperi Avatar answered Oct 21 '22 22:10

Asperi


For the question in your title, no. For example, here is some code that doesn't work:

class Storage {
    var value = 0
}

@propertyWrapper
struct MyState: DynamicProperty {
    var storage = Storage()

    var wrappedValue: Int {
        get { storage.value }

        nonmutating set {
            storage.value = newValue // This doesn't work
        }
    }
}

So apparently you still need to put a State or ObservedObject etc. inside your DynamicProperty to trigger an update as Asperi did, a DynamicProperty itself doesn't enforce any update.

like image 20
Cosyn Avatar answered Oct 22 '22 00:10

Cosyn