How can I connect Binding value of View to Published value of ObservableObject?
The goal: all changes of objValue will be reflected on viewValue and vice versa.
Note: Do not propose direct onChange(obj.objValue) { viewValue = $0 } modifier. It will trigger extra drawing cycle of View (first for objValue and second for viewValue).
class MyObject: ObservableObject {
@Published var objValue: Int = 0
}
struct MyView: View {
@Binding var viewValue: Int
@StateObject var obj = MyObject()
var body: some View {
Text("Placeholder")
.onChange(viewValue) {
//Do something
}
}
}
Here is the working solution (not directly with Combine yet) that is utilising the View adapter that let us to avoid extra redraw of the MyView body. By passing the Binding value to ValueReader View, only its body will be triggered to redraw, then it is just passing the new result outside and we can work with it. Here we assign the updated value of viewValue to objValue.
This technique is avoiding extra redraw cycles, MyView body will be redrawn only ONCE, no matter if objValue or viewValue was changed first. Because viewValue is not being used directly in the body, only ValueReader will be redrawn directly on viewValue change skipping MyView's body redraw.
class MyObject: ObservableObject {
@Published var objValue: Int = 0
}
struct MyView: View {
@Binding var viewValue: Int
@StateObject var obj = MyObject()
var body: some View {
ZStack {
ValueReader(value: $viewValue) { newValue in
obj.objValue = newValue //Mirroring viewValue to obj.objValue
}
Text("Placeholder")
.onChange(of: obj.objValue, perform: handleValue)
}
}
private func handleValue(_ value: Int) {
viewValue = value //Mirroring obj.objValue to viewValue
//Do any job here. For example just send analytics
}
private struct ValueReader: View {
@Binding var value: Int
let onChange: (_ newValue: Int) -> ()
var body: some View {
Color.clear
.onChange(of: value) { newValue in
onChange(newValue)
}
}
}
}