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)
}
}
}
}
将导致此行为:
这是由于 Timer
和 RunLoop
的性质造成的。改用 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)
}
}
给定 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)
}
}
}
}
将导致此行为:
这是由于 Timer
和 RunLoop
的性质造成的。改用 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)
}
}