Reputation: 49590
I'm trying to load a remote resource, so I'm using .onAppear
to do that. However, when I use if/else
inside the view, it causes the it to fire twice.
class FooVM: ObservableObject {
@Published var remoteText: String? = nil
func load() {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.remoteText = "\(Date())"
}
}
}
struct Foo: View {
@StateObject private var vm = FooVM()
var body: some View {
content
.onAppear(perform: vm.load)
}
@ViewBuilder
private var content: some View {
if let text = vm.remoteText {
Text(text)
} else {
Circle()
}
}
}
struct ContentView: View {
var body: some View {
TabView {
Foo()
.tabItem {
Image(systemName: "circle.fill")
Text("Home")
}
}
}
}
EDIT: Turns out it only repro's when the content is inside a TabView
. I updated the code above
Why is onAppear
(and thus load
) firing twice? Actually, in the Playground, it fires repeatedly every 2 seconds, but in a simulator in fires only twice.
How do I avoid it? Bear in mind that this is a simplified example.
Interestingly, if it wasn't a choice between Text
and Circle
, but just a different text within Text
, then it would fire only once:
Text(vm.remoteText ?? "loading...")
Upvotes: 2
Views: 1478
Reputation: 4245
When your content is switching within the if statement, Foo() has to completely re-appear. The solution is to embed the content of Foo() within a ZStack (or similar) so that it doesn't need to re-appear the entire view and call .onAppear again. Here, the ZStack remains appeared while the content switches.
var body: some View {
ZStack {
content
}
.onAppear(perform: vm.load)
}
Upvotes: 2