[28/10/2019:我已经用UIKit重新创建了相同的测试项目...不足为奇,计时器不会重新加载任何视图控制器视图!因此,这看起来像是SwiftUI错误。 UIKIt版本:https://github.com/DominikButz/DispatchSourceTimerUIKit.git]
[2019年10月19日:请查看https://github.com/DominikButz/DispatchTimerSwiftUIReloadBug.git,以更好地了解此问题。在模拟器中启动时,只需点击“启动计时器”。检查调试控制台,您将看到每秒重新加载所有视图。]
我正在SwiftUI中创建一个iOS应用,需要为此使用计时器。我在下面发布课程。
计时器可以正常工作(除非应用程序在后台运行时不继续,但这又是另一回事了……)。如您在resumeTimer函数中所见,计时器设置为每秒重复一次。
有一个视图,其中在每次触发计时器时都会更新“文本”标签。这很好。但是,问题在于,在计时器启动后(即每秒),所有在计时器启动后加载的其他视图都将重新加载。例如每秒重新加载不相关的列表(未放置计时器文本标签的),这使其不可用。
我知道这是因为,当我通过用户界面暂停或取消计时器时,调试器显示视图不再每秒都重新加载。我在每个视图的主体中放置了一个断点(带有“评估后继续”选项),调试器显示每次计时器触发时都会重新加载视图。
下面的计时器类在SceneDelegate中创建,然后作为环境对象传递到“内容”视图。我之前仅在放置计时器Text标签的视图中将其作为ObservedObject,但结果是相同的。参见下面的视图,该计时器会在计时器触发时重新加载...(gif屏幕截图)。
这可能是SwiftUI中的错误吗?或者我的计时器类可能有错误?感谢您向正确方向的提示
这是内容视图
struct ContentView: View {
@EnvironmentObject var workoutModel: WorkoutModel
@EnvironmentObject var workoutWatch: WorkoutStopWatchTimer
@State var selectedMenuIndex = UserDefaults.standard.integer(forKey: UserDefaultKeys.selectedMenuIndex)
@State private var workoutViewPresenting = false
func setMenuIndex() {
UserDefaults.standard.set(selectedMenuIndex, forKey: UserDefaultKeys.selectedMenuIndex)
}
var body: some View {
TabView(selection:$selectedMenuIndex) {
WorkoutSelectionView(workoutViewPresenting: self.$workoutViewPresenting).onAppear(perform: self.setMenuIndex)
.tabItem {
Image(systemName: "1.square.fill")
Text("Start Workout")
}.tag(0)
Text("History").onAppear(perform: self.setMenuIndex) // I set a breakpoint here (with continue after evaluation checked)
.tabItem {
Image(systemName: "2.square.fill")
Text("History")
}.tag(1)
ExerciseListView().onAppear(perform: self.setMenuIndex) // I set a breakpoint here (with continue after evaluation checked)
.tabItem {
Image(systemName: "3.square.fill")
Text("Exercises")
}.tag(2)
Text("Profile").onAppear(perform: self.setMenuIndex)
.tabItem {
Image(systemName: "person.fill")
Text("Profile")
}.tag(3)
}.sheet(isPresented: self.$workoutViewPresenting) {
// the timer is passed to this view in which a Text label is updated every second to show the time
WorkoutView().environmentObject(self.workoutModel).environmentObject(self.workoutWatch)
}
}
}
调试器控制台:查看我在上方的内容视图中添加的注释,以查看放置断点的位置。我没有多次打开和关闭这些视图,它们在计时器运行时每秒自动重新加载。
... 3加载历史 3 loading练习列表视图 4加载历史 4 loading练习列表视图 5加载历史 5 loading练习列表视图 6加载历史 6 loading练习列表视图 7加载历史 7 loading练习列表视图 ...
WorkoutStopWatchTimer类:ObservableObject {
private var sourceTimer: DispatchSourceTimer?
private let queue = DispatchQueue.init(label: "stopwatch.timer", qos: .background, attributes: [], autoreleaseFrequency: .never, target: nil)
private var counter: Int = 0 // seconds
var endDate: Date?
var duration: TimeInterval?
@Published var timeDisplayString = "0:00"
var paused = false
var isActive: Bool {
return self.sourceTimer != nil
}
func start() {
self.paused = false
guard let _ = self.sourceTimer else {
self.startTimer()
return
}
self.resumeTimer()
}
func finish() {
guard self.sourceTimer != nil else {return}
self.endDate = Date()
self.duration = TimeInterval(exactly: Double(self.counter))
self.sourceTimer?.setEventHandler {}
self.sourceTimer?.cancel()
if self.paused == true {
self.sourceTimer?.resume()
}
self.sourceTimer = nil
self.reset()
}
func pause() {
self.paused = true
self.sourceTimer?.suspend()
}
private func reset() {
self.timeDisplayString = "0:00"
self.counter = 0
}
private func startTimer() {
self.sourceTimer = DispatchSource.makeTimerSource(flags: DispatchSource.TimerFlags.strict,
queue: self.queue)
self.resumeTimer()
}
private func resumeTimer() {
self.sourceTimer?.setEventHandler { [weak self] in
// self.eventHandler = {
self?.updateTimer()
// }
}
self.sourceTimer?.schedule(deadline: .now(),
repeating: 1)
self.sourceTimer?.resume()
}
private func updateTimer() {
self.counter += 1
DispatchQueue.main.async {
self.timeDisplayString = WorkoutStopWatchTimer.convertCountToTimeString(counter: self.counter)
}
}
}