简体   繁体   中英

SwiftUI: @ObservedObject redraws every view

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?

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.

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.

Eg;

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM