Swift DispatchSourceTimer заставляет несвязанные представления перезагружаться каждую секунду - PullRequest
1 голос
/ 18 октября 2019

[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? или может быть ошибка в моем классе таймера? спасибо за любые подсказки в правильном направлении

the view gets reloaded every time the timer fires

Вот представление содержимого

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


}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...