SwiftUI 使用高频数据更新 UI

SwiftUI updating UI with high frequency data

我正在尝试使用来自单独后台线程的高频数据更新主视图。 我创建了两个选项卡视图,如果更新速度较慢,我可以更改视图。但在另一种情况下 UI 没有反应。我只在真实设备上观察到这种行为,在模拟器中一切正常。

while 循环仍然代表一个 imu,只是为了简单起见。

有人知道如何解决这个问题吗?

非常感谢!

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()
    }
}

您是否尝试过使用 combine 解决该问题? 如果您的 ui 更新太快,您可以收集一些数据,将其合并到一种缓冲区中,然后在缓冲区已满后,您可以将其推送到您的视图中。另一种方法是针对特定延迟对输入进行去抖动,然后仅将最后一次更新推送到您的视图。例如,假设您有发布的变量 i 不断更新它的值。您可以引入以下视图模型:

class Loop: ObservableObject {
    @Published var i: Int = 0
    @Published var updatedVariable: Int = 0
    private var bag = Set<AnyCancellable>()

    init() {
        $i
          .debounce(for: 0.5, scheduler: DispatchQueue.main)
          .sink { [weak self] value in 
              self?.updatedVariable = value
          }
          .store(in: &bag)
        startLoop()
    }

    func startLoop() {
        while true {
            print("i = \(self.i)")
            DispatchQueue.main.async {
                self.i += 1
            }
        }
    }
}

如果您使用 updatedVariable 而不是 i 变量,这样您就可以每 0.5 秒更新一次 ui。

您需要将频率数据的更新存储与表示的 UI 部分分开,因此存储 receives/contains 实际的真实数据,但 UI 部分更新只要您只需要 (0.5秒、1 秒、5 秒等)

这是可能的方法。测试 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()
        }
    }
}