Consider the following property wrapper. It keeps track of value in wrappedValue
, and also caches old value in wrappedValueOld
.
@propertyWrapper struct OldValueCache<Value>: DynamicProperty {
@State private var field: Value
var wrappedValue: Value {
get {
field
}
nonmutating set {
wrappedValueOld = field
field = newValue
}
}
@State private(set) var wrappedValueOld: Value?
init(wrappedValue: Value) {
self._field = .init(initialValue: wrappedValue)
}
}
To test it, consider the following View
:
struct ContentView: View {
@OldValueCache var a: Int = 0
var body: some View {
VStack(content: {
Text("\(a) \(_a.wrappedValueOld.map { .init($0) } ?? "nil")")
Button("Increment", action: { a += 1 })
})
.onAppear(perform: foo)
}
private func foo() {
@OldValueCache var b: Int = 0
print(b, _b.wrappedValueOld.map { .init($0) } ?? "nil")
b += 1
print(b, _b.wrappedValueOld.map { .init($0) } ?? "nil")
}
}
It contains a Text
that displays current and old value, and a Button
that increments that value. By tapping on the button, we can see a proper behavior. Text
shows "0 nil", then "1 0", "2 1", "3 2", etc.
However, variable with that property wrapper breaks in a method. foo
is called on onAppear(perform:)
which should print "0 nil" and then "1 0". But, "0 nil" is printed each time. DynamicProperty
protocol
seems to cause this.
Alternately, we can remove conformance and end up with:
@propertyWrapper struct OldValueCache<Value> {
private var field: Value
var wrappedValue: Value {
get {
field
}
set {
wrappedValueOld = field
field = newValue
}
}
private(set) var wrappedValueOld: Value?
init(wrappedValue: Value) {
self.field = wrappedValue
}
}
This time, foo
method works properly, but since OldValueCache
is no longer dynamic, body
breaks, and Button
needs to be commented out.
Is this expected behavior? What are the possible solutions to make a property wrapper work in both cases, without duplicating the declaration?