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 vi
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):
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
}
}
}
}
struct TestPropertyWrapper_Previews: PreviewProvider {
static var previews: some View {
TestPropertyWrapper()
}
}
Yes this is correct, here is an example:
class SomeObservedObject : ObservableObject {
@Published var counter = 0
}
@propertyWrapper struct Foo: DynamicProperty {
@StateObject var object = SomeObservedObject()
public var wrappedValue: Int {
get {
object.counter
}
nonmutating set {
object.counter = newValue
}
}
}
When a View using @Foo
is recreated, SwiftUI passes the Foo struct the same object as last time, so has the same counter value. When setting the foo var, this is set on the ObservableObject
's @Published
which SwiftUI detects as a change and causes the body to be recomputed.
Try it out!
struct ContentView: View {
@State var counter = 0
var body: some View {
VStack {
Text("\(counter) Hello, world!")
Button("Increment") {
counter = counter + 1
}
ContentView2()
}
.padding()
}
}
struct ContentView2: View {
@Foo var foo
var body: some View {
VStack {
Text("\(foo) Hello, world!")
Button("Increment") {
foo = foo + 1
}
}
.padding()
}
}
When the second button is tapped the counter stored in Foo
is updated. When first button is tapped, ContentView
's body is called and ContentView2()
is recreated but keeps the same counter as last time. Now SomeObservedObject
can be a NSObject
and implement a delegate
protocol for example.