I'm trying to update the main view with high frequency data coming from separate background thread. I've created two tabviews and in case of slow update rate I can change the view. But in another case the UI doesn't react. I've observed this behavior only on real device, in the simulator works everything fine.
The while loop is still representing an imu, just to keep it simple.
Did someone any idea how to fix this issue?
Many thanks!
import SwiftUI
struct ContentView: View {
@EnvironmentObject var loop : Loop
var body: some View {
TabView{
VStack {
Text("Content View")
LoopView()
}.tabItem{
VStack{
Text("tab1")
Image(systemName: "car")
}
}
Text("second view").tabItem{
VStack{
Text("tab2")
Image(systemName: "star")
}
}
}
}
}
class Loop : ObservableObject {
@Published var i : Int
func startLoop() {
while true {
print("i = \(self.i)")
DispatchQueue.main.async {
self.i += 1
}
//sleep(1) // comment out to simulate worst case
}
}
init() {
DispatchQueue.global(qos: .background).async {
self.startLoop()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
You need to separate updating storage of frequency data from represented UI part, so storage receives/contains actual real data, but UI part update as soon as you only want (0.5 sec, 1 sec, 5 secs, etc.)
Here is possible approach. Tested with Xcode 12 / iOS 14.
import Combine
class Loop : ObservableObject {
private var storage: Int = 0
private var counter = PassthroughSubject<Int, Never>()
@Published var i : Int = 0 // only for UI
func startLoop() {
while true {
storage += 1 // update storage
counter.send(storage) // publish event
}
}
private var subscriber: AnyCancellable?
init() {
subscriber = counter
.throttle(for: 0.5, scheduler: DispatchQueue.global(qos: .background), latest: true) // drop in background
.receive(on: DispatchQueue.main) // only latest result
.sink { [weak self] (value) in // on @pawello2222 comment
self?.i = value
}
DispatchQueue.global(qos: .background).async {
self.startLoop()
}
}
}