If I use @State
to track selectedTab
for TabView
then the View
's of the Tab will NOT get invalidated (their init
is NOT called).
However, if I use @ObservedObject
to track selectedTab
for TabView
then the View
's of the Tab WILL get invalidated (their init
IS called`).
Why? I'm simply moving from @State
to @ObservedObject
yet its doing different things.
Below is code that can be plugged into a Playground. Switch between TabView
initializers to see the difference. If we use @State
then the "Init called for OneView!"
will be printed once. If we use @ObservedObject
then "Init called for OneView!"
will be called on every tab switch.
import SwiftUI
import PlaygroundSupport
enum Tab: String {
case one
case two
}
struct ContentView: View {
@State var selectedTab: Tab = .one
@ObservedObject var selectedTabObject: SelectedTabAsObject
init(selectedTabObject: SelectedTabAsObject) {
self.selectedTabObject = selectedTabObject
print("Init called for ContentView!")
}
var body: some View {
// TODO: Switch between commenting out the bottom 2 lines
TabView(selection: $selectedTab) {
// TabView(selection: $selectedTabObject.selectedTab) {
OneView()
.tabItem {
Text("One")
}
.tag(Tab.one)
TwoView()
.tabItem {
Text("Two")
}
.tag(Tab.two)
}
}
}
class SelectedTabAsObject: ObservableObject {
@Published var selectedTab: Tab = .one
init() {
print("Init called for SelectedTabAsObject!")
}
}
struct OneView: View {
init() {
print("Init called for OneView!")
}
var body: some View {
Text("One View")
}
}
struct TwoView: View {
var body: some View {
Text("Two View")
}
}
struct AppView: View {
let selectedTabObject: SelectedTabAsObject
init() {
self.selectedTabObject = SelectedTabAsObject()
print("Init called for AppView!")
}
var body: some View {
ContentView(selectedTabObject: selectedTabObject)
}
}
PlaygroundPage.current.setLiveView(AppView())
After I posted this, I think I know the reason (although I don't really fully understand it).
@State
is keeping a track of a simple value (enum/ String
).
@ObservedObject
is an object.
When SwiftUI asks: "Should I reload body?" it needs to know what changed. In the case of @State
it knows that only the value changed. In the case of @ObservedObject
, it's not sure so it reloads the whole body
.
The fix would likely be to implement Equatable
for the View
to avoid unnecessary body
reloads. However, the Equatable
method doesn't get called even when marking ContentView
as .equatable
. There might be some SwiftUI bugs.
The following post has some notes about this:
Here is a post on Equatable
and .equatable()
:
Interesting discussion about how SwiftUI does comparisons:
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.