Scenario
A simple SwiftUI App
that consists of a TabView
with two tabs. The App
struct has a @StateObject
property, which is being repeatedly and very quickly (30 times per second) updated by simulateFastStateUpdate
.
In this example, simulateFastStateUpdate
is not doing any useful work, but it closely resembles a real function that quickly updates the app's state. The function does some work on a background queue for a short interval of time and then schedules a state update on the main queue. For example, when using the camera API, the app might update the preview image as frequently as 30 times per second.
Question
When the app is running, the TabView
does not respond to taps. It's permanently stuck on the first tab. Removing liveController.message = "Nice"
line fixes the issue.
- Why is
TabView
stuck? - Why is updating
@StateObject
causing this issue? - How to adapt this simple example, so that the
TabView
is not stuck?
import SwiftUI
class LiveController: ObservableObject {
@Published var message = "Hello"
}
@main
struct LiveApp: App {
@StateObject var liveController = LiveController()
var body: some Scene {
WindowGroup {
TabView() {
Text(liveController.message)
.tabItem {
Image(systemName: "1.circle")
}
Text("Tab 2")
.tabItem {
Image(systemName: "2.circle")
}
}
.onAppear {
DispatchQueue.global(qos: .userInitiated).async {
simulateFastStateUpdate()
}
}
}
}
func simulateFastStateUpdate() {
DispatchQueue.main.async {
liveController.message = "Nice"
}
// waits 33 ms ~ 30 updates per second
usleep(33 * 1000)
DispatchQueue.global(qos: .userInitiated).async {
simulateFastStateUpdate()
}
}
}