Dribbler
Dribbler

Reputation: 4701

watchOS timer app freezes UI in dimmed state while haptics continue working

I'm working on a watchOS timer app that needs to run for 3 1/2 minutes while showing countdown numbers. I think that I've discovered that there's nothing to do with the watch dimming- that's a system setting that the watch wearer sets for 15 or 70 seconds. But the face can update while dimmed--I see that in the native timer app for example. This is a tricky one to debug, as when the physical watch is attached to Xcode as a device, it doesn't dim or freeze the face at all. It's only when I restart the app on the physical watch that I see the issues, and there are 2:

  1. With the first dimming, it skips a number. The timing is fine, it's just that the number that would show when the watch dims does not. This is not mission-critical, but it is annoying.
  2. After about 2 1/2 minutes of running, the UI completely freezes but the haptic feedback continues working correctly. When the watch is tapped, the display updates to the correct number. This is a bug that I'd like to fix.

This is what I've tried so far:

Here's my current code using DispatchSourceTimer and WKExtendedRuntimeSession:

class TimerManager: NSObject, ObservableObject {
    @Published var phase: ExercisePhase = .notStarted
    @Published var currentSet = 1
    @Published var isSqueezing = true
    @Published var secondsRemaining = 10
    @Published var isPaused = false
    
    private var runtimeSession: WKExtendedRuntimeSession?
    private var timerSource: DispatchSourceTimer?
    private var uiRefreshWorkItem: DispatchWorkItem?
    
    func scheduleNextUpdate() {
        guard !isPaused && phase != .completed && phase != .notStarted else { return }
        
        stopSession()
        
        // Start new session
        runtimeSession = WKExtendedRuntimeSession()
        runtimeSession?.start()
        
        // Create main timer for exercise logic
        let timer = DispatchSource.makeTimerSource(queue: .main)
        timer.schedule(deadline: .now(), repeating: .seconds(1))
        timer.setEventHandler { [weak self] in
            self?.updateExercise()
        }
        
        // Create UI refresh mechanism
        scheduleUIRefresh()
        
        timerSource = timer
        timer.resume()
    }
    
    private func scheduleUIRefresh() {
        uiRefreshWorkItem?.cancel()
        
        let workItem = DispatchWorkItem { [weak self] in
            guard let self = self else { return }
            
            DispatchQueue.main.async {
                self.objectWillChange.send()
            }
            
            if !self.isPaused && self.phase != .completed {
                self.scheduleUIRefresh()
            }
        }
        
        uiRefreshWorkItem = workItem
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.25, execute: workItem)
    }
}

The app uses watchOS's Physical Therapy session type. The core timing functionality (including haptics) works perfectly, but the UI updates are frozen in the dimmed state at the times that I described.

I've also tried:

Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true)

and

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

But the same problems persist.

Has anyone encountered similar problems with watchOS UI updates in a dimmed state but with the code happily running in the background?

I'm coding in Xcode 16.2 and Swift 6.0.3 and testing on a physical Apple Watch Series 8 with watchOS 11. Any emulator I try works fine. Thanks super in advance if you can weigh in.

Upvotes: 0

Views: 32

Answers (0)

Related Questions