[28/10/2019: я воссоздал тот же тестовый проект с UIKit ... неудивительно, что таймер не заставляет перезагружаться ни одно из представлений контроллера представления !!! Так что это похоже на ошибку SwiftUI. Версия UIKIt: https://github.com/DominikButz/DispatchSourceTimerUIKit.git]
[19/10/2019: пожалуйста, ознакомьтесь с https://github.com/DominikButz/DispatchTimerSwiftUIReloadBug.git для лучшего понимания проблемы. Когда вы запустите это в симуляторе, просто нажмите «Запустить таймер». Проверьте консоль отладки, вы увидите, что все представления перезагружаются каждую секунду.]
Исходное сообщение:
Я создаю приложение для iOS в SwiftUI, для которого мне нужно использовать таймер. Я публикую класс ниже.
Таймер работает нормально (за исключением того, что приложение не работает в фоновом режиме, но это уже другая история ...). Как вы можете видеть в функции resumeTimer, таймер настроен на повторение каждую секунду.
Существует представление, в котором текстовая метка обновляется при каждом срабатывании таймера. Это отлично работает. Однако проблема в том, что все виды, которые загружаются после запуска таймера, перезагружаются при каждом срабатывании таймера, то есть каждую секунду. например, несвязанный список (, где текстовая метка таймера не размещена ) перезагружается каждую секунду, что делает его непригодным для использования.
Я знаю это, потому что когда я приостанавливаю или отменяю таймер через пользователяИнтерфейс, отладчик показывает, что представления больше не перезагружаются каждую секунду. Я поместил точку останова (с опцией «продолжить после оценки») в теле каждого из этих представлений, и отладчик показывает, что представления перезагружаются при каждом срабатывании таймера.
Класс таймера, созданный ниже, создается вSceneDelegate и затем передается в представление Content как объект среды. У меня было это как ObservedObject раньше только в представлении, где размещена метка таймера Text, но результат тот же. См. Ниже представление, которое перезагружается при срабатывании таймера ... (скриншот 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)
}
}
}
консоль отладчика: см. комментарии Iпоместите в представлении содержимого выше, чтобы увидеть, где я разместил точки останова. Я не открывал и не закрывал эти виды много раз, они автоматически перезагружаются каждую секунду при работе таймера.
... 3 загрузка истории 3 загрузка списка упражнений 4 загрузка истории 4 загрузка списка упражнений 5loading History 5 loading Список упражнений View 6 loading History 6 loading Список упражнений View 7 loading History 7 loading Просмотр списка упражнений ...
class 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)
}
}
}