SwiftUI: @ObservedObject redraws every view

老子叫甜甜 提交于 2021-01-29 20:12:02

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!