Apple Watch display indicator pauses NSTimer, but WKInterfaceTimer keeps counting

According to Apple's documentation, you will use both WKInterfaceTimer (local to Watch, counting down, but not triggering any event on completion) and NSTimer (to trigger methods when the timer ends). So, I have both NSTimer and WKInterfaceTimer in my Application Interface Controller. On the simulator, in all circuits when the WatchApp is running, the NSTimer and WKInterfaceTimer continue to count down (as they should) when the Watch is either idle or idle (using the simulator lock / unlock as specified in the Apple manual).

However, on a real physical Watch, the 2 timers behave differently in display standby (dimmed) and wakeup states. Hibernate PAUSES NSTimer of the interface controller, but WKInterfaceTimer continues to count down (as it should).

So, the two timers don't work immediately after the first physical sleep of the Apple Watch (NSTimer pauses, WKInterfaceTimer continues to count). Looking for other experiences and has anyone implemented a good way to sync both NSTimer and WKInterfaceTime regardless of Watch mode (sleep or awake).

+3


source to share


4 answers


It seems that you can save the countdown end time (like in NSUserDefaults

), then willActivate

re-install the NSTimer so that it ends at the right time. Alternatively, you can call your iPhone app to schedule a local notification, but there is no guarantee that the notification will be delivered to your watch, so this may not work for you.



+2


source


The end of my research is that according to Apple documentation for the current version of WatchKit, you will need 2 timers: the WK timer on the Apple Watch and the NSTimer on the iPhone. 2 timers should start / start synchronously, WK Timer keeps counting down as in standby / sleep mode, NSTimer job should trigger alarm / dispatch notification when timer expires.



To sync both timers, you need to start the iPhone NSTimer immediately when the user starts the Watch Apple Watch WK timer.

+1


source


Just start / stop WKInterfaceTimer in willActivate / didDeactivate:

class MyAwesomeInterfaceController: WKInterfaceController {

    @IBOutlet var timer: WKInterfaceTimer!

    override func willActivate() {
        super.willActivate()
        timer.start()
    }

    override func didDeactivate() {
        super.didDeactivate()
        timer.stop()
    }

}

      

0


source


When the Apple Watch screen disappears, the app will deactivate and suspend Timer

(s) that you launched until the app is brought back to the foreground.
This does not happen in the Simulator, but it does cause a problem on a real device.
However, it is WKInterfaceTimer

not affected because it is based on a future date and is handled internally.

So, to keep the timer in sync after the app is brought back to the foreground can be done simply by comparing the 2 dates in the block Timer

and observing the difference between the two dates.

In the following example, if all you want to do is keep the timer updated and know when the countdown has finished, the following should be sufficient:

//Globally declared
@IBOutlet var interfaceTimerCountDown: WKInterfaceTimer!

var countdownToDate: Date?

func startCountdown(from count: TimeInterval) {
    //Create a future date to which the countdown will count to
    countdownToDate = Date().addingTimeInterval(count)

    //Set and Start the WKInterfaceTimer
    interfaceTimerCountDown.setDate(countdownToDate!)
    interfaceTimerCountDown.start()

    //Start your own Timer
    Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] (timer) in
        //Get current date
        let currentDate = Date()

        //Get difference between future date and current date
        let dxTimeInSeconds = self?.countdownToDate!.timeIntervalSince(currentDate) ?? 0
        print(dxTimeInSeconds)

        //Check if countdown has completed
        if dxTimeInSeconds <= 0 {
            //...do something
            print("Countdown complete!")

            timer.invalidate()
        }
    }
}

      


But...

WKInterfaceTimer

and Timer

will be out of sync for a few milliseconds, so if you want to update the user interface exactly at the same time, when the counter WKInterfaceTimer

is updated, the logic is not enough.
In my case, I wanted to update the image; like a ring animation and the only way I have communicated is to reset WKInterfaceTimer

for WKInterfaceLabel

+ a WKInterfaceGroup

and manually update the label and background image of the group in the timer block.

Custom solution:

//Declared globally

//for image; to simulate a ring animation image
@IBOutlet var group_lblTimerCount: WKInterfaceGroup!

//Simple label inside a WKInterfaceGroup
@IBOutlet var lblTimerCount: WKInterfaceLabel! //inside group_lblTimerCount

var countdownToDate: Date?

func startCountdown(from count: Int) {
    //Create a future date to which the countdown will count to
    countdownToDate = Date().addingTimeInterval(TimeInterval(count))

    //Update Label and UI
    updateTimerLabel(to: count,
                     from: count)

    //Start your own Timer
    Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] (timer) in
        //Get current date
        let currentDate = Date()

        //Get difference between future date and current date
        let dxTimeInSeconds = self?.countdownToDate!.timeIntervalSince(currentDate) ?? 0

        //Update Label and UI
        let dxTimeInSeconds_Int = Int(round(dxTimeInSeconds))
        self?.updateTimerLabel(to: dxTimeInSeconds_Int,
                               from: count)

        //Check if countdown has completed
        if dxTimeInSeconds <= 0 {
            //...do something
            print("Countdown complete!")

            //Stop timer
            timer.invalidate()
        }
    }
}

func updateTimerLabel(to count: Int, from total: Int) {
    lblTimerCount.setText("\(count)")

    updateTimerRing(to: count,
                    from: total)
}

func updateTimerRing(to count: Int, from total: Int) {
    /*
     I have 60 images for the ring named from "ring60" to "ring0"
     Generated at: http://hmaidasani.github.io/RadialChartImageGenerator
     */

    let numberOfImages = 60 //The total number of images you have
    let imageIndex = "\(Int(count * numberOfImages/total))"
    let imageName = "ring\(imageIndex)"

    group_lblTimerCount.setBackgroundImageNamed(imageName)
}

      

PS: I tried to find an elegant solution for all this, but could not find a suitable example, so I am sharing what I got.

Hope this helps someone :)

0


source







All Articles