问题
I try to implement the MVVM way correctly in SwiftUI, so I came up with this (simplified) Model and ViewModel:
struct Model {
var property1: String
var property2: String
}
class ViewModel: ObservableObject {
@Published var model = Model(property1: "this is", property2: "a test")
}
Using this in a View works fine, but I experienced some bad performance issues, as I extended the ViewModel with some computed properties and some functions (as well the Model itself is more complicated). But let's stay with this example, because it demonstrates perfectly, what I think is a big problem in SwiftUI itself.
Imagine, you have those views to display the data:
struct ParentView: View {
@ObservedObject var viewModel: ViewModel
var body: some View {
print("redrawing ParentView")
return ChildView(viewModel: self.viewModel)
}
}
struct ChildView: View {
@ObservedObject var viewModel: ViewModel
var body: some View {
print("redrawing ChildView")
return VStack {
ViewForTextField(property: self.$viewModel.model.property1)
ViewForTextField(property: self.$viewModel.model.property2)
}
}
}
struct ViewForTextField: View {
@Binding var property: String
var body: some View {
print("redrawing textView of \(self.property)")
return TextField("...", text: self.$property)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
}
Now entering text into one of the TextField leads to a redraw of every View in my window! The print output is:
redrawing ParentView
redrawing ChildView
redrawing textView of this is
redrawing textView of a test
redrawing ParentView
redrawing ChildView
redrawing textView of this isa
redrawing textView of a test
redrawing ParentView
redrawing ChildView
redrawing textView of this isab
redrawing textView of a test
...
As I can see, SwiftUI redraws every view, because every view is listening to the ObservedObject.
How can I tell SwiftUI, that it only should redraw those views, where really happened any changes?
回答1:
Actually MVVM means own model for every view, which is updated on model changes, not one model for all views.
So there is no needs to observe viewModel in ParentView as it does not depend on it
struct ParentView: View {
var viewModel: ViewModel // << just member to pass down in child
var body: some View {
print("redrawing ParentView")
return ChildView(viewModel: self.viewModel)
}
}
Alternate is to decompose view model so every view has own view sub-model, which would manage updates of own view.
回答2:
If all your views observe the same thing, and that thing changes, then all your views will re-render. This is by definition. There's no option to configure to optionally update certain views.
That being said, you can work around it by manipulating what changes you'd like to publish.
E.g.;
class ViewModel: ObservableObject {
@Published var model = Model(property1: "this is", property2: "a test")
var mytext = "some text" // pass this as binding instead of model.propertyX
}
Now when textfield changes, mytext changes, but this change won't be published thus won't trigger further view updates. And you still can access it from view model.
I personally would recommend using @State and @EnvironmentObject instead of "view model". They are the built-in way to handle binding and view updates with tons of safe-guards and supports.
Also you should use value type instead of reference type as much as possible. MVVM from other languages didn't take this into consideration.
来源:https://stackoverflow.com/questions/61480230/swiftui-observedobject-redraws-every-view