SwiftUI 在列表滚动期间停止更新

SwiftUI stops updates during scrolling of List

给定 SwiftUI 中的列表,一旦开始平移,列表中视图的更新似乎会暂停,直到滚动停止。有什么办法可以避免这种情况吗?

考虑以下代码:

class Model: ObservableObject, Identifiable {
    @Published var offset: CGFloat = 0

    let id = UUID()
    private var timer: Timer!

    init() {
        timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true, block: { _ in
            self.update()
        })
    }

    func update() {
        offset = CGFloat.random(in: 0...300)
    }
}

struct ContentView: View {
    @ObservedObject var model1 = Model()
    @ObservedObject var model2 = Model()
    @ObservedObject var model3 = Model()
    @ObservedObject var model4 = Model()

    var body: some View {
        List {
            ForEach([model1, model2, model3, model4]) {
                Rectangle()
                    .foregroundColor(.red)
                    .frame(width: [=10=].offset, height: 30, alignment: .center)
                    .animation(.default)
            }
        }
    }
}

将导致此行为:

这是由于 TimerRunLoop 的性质造成的。改用 GCD,就像下面的方法

init() {
    var runner: (() -> ())?
    runner = {
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
            if let self = self {
                self.update()
                runner?()
            }
        }
    }
    runner?()
}

您可以像 Asperi 的回答那样使用 GCD,但这并不能解释为什么您的代码不起作用。

问题是,当滚动视图跟踪您的触摸时,它 运行 是 .tracking 模式下的 运行 循环。但是因为您使用 scheduledTimer(withTimeInterval:repeats:block:) 创建了 Timer,所以 Timer 仅在 .default 模式下设置为 运行。

您可以将计时器添加到所有常见的 运行 循环模式(包括 .tracking),如下所示:

RunLoop.main.add(timer, forMode: .common)

但我可能会改用 Combine 发布商,如下所示:

class Model: ObservableObject, Identifiable {
    @Published var offset: CGFloat = 0

    let id = UUID()

    private var tickets: [AnyCancellable] = []

    init() {
        Timer.publish(every: 0.5, on: RunLoop.main, in: .common)
            .autoconnect()
            .map { _ in CGFloat.random(in: 0...300) }
            .sink { [weak self] in self?.offset = [=11=] }
            .store(in: &tickets)
    }
}