Apple Watch 显示器睡眠暂停 NSTimer,但 WKInterfaceTimer 一直倒计时

Posted

技术标签:

【中文标题】Apple Watch 显示器睡眠暂停 NSTimer,但 WKInterfaceTimer 一直倒计时【英文标题】:Apple Watch display sleep pauses NSTimer, but WKInterfaceTimer keeps counting down 【发布时间】:2015-07-04 16:28:00 【问题描述】:

根据 Apple 文档,将同时使用 WKInterfaceTimer(Watch 本地,倒计时但在结束时不触发任何事件)和 NSTimer(在计时器结束时触发方法)。所以,我的 App Interface Controller 中有一个 NSTimer 和一个 WKInterfaceTimer。在模拟器上,在 WatchApp 运行时的所有方案中,NSTimer 和 WKInterfaceTimer 在 Watch 处于唤醒或睡眠模式时(按照 Apple 手册中的说明使用模拟器锁定/解锁)会继续倒计时(应该如此)。

但是,在真正的物理 Watch 上,这 2 个计时器在 Watch 显示睡眠(停电)和唤醒状态时表现不同。睡眠模式暂停了接口控制器的 NSTimer,但 WKInterfaceTimer 一直在倒计时(应该如此)。

因此,第一次物理 Apple Watch 睡眠时,2 个计时器立即不同步(NSTimer 暂停,WKInterfaceTimer 继续倒计时)。寻求其他人的经验,以及是否有人实施了一种使 NSTimer 和 WKInterfaceTime 保持同步的好方法,无论 Watch 模式如何(睡眠或清醒)。

【问题讨论】:

【参考方案1】:

您似乎可以存储倒计时的结束时间(例如,在NSUserDefaults 中),然后在willActivate 上,重新设置您的 NSTimer 以便它在正确的时间完成。或者,您可以调用您的 iPhone 应用程序来安排本地通知,但不能保证通知会发送到您的手表,因此这可能不适合您。

【讨论】:

谢谢,迈克。我曾想过使用 didDeactivate(睡眠,存储)和 willActivate(唤醒,再次检索和重置 NSTimer),但这会发生很多次,因为睡眠/唤醒如此频繁(每次你扭动手腕时)。我尝试了 iPhone 的方法,通知实际上被传递到了 Watch(ios 确定了这一点),但是当 Watch 理论上应该在本地轻松处理时间事件(没有 iPhone)时,这听起来像是不必要的开销。仍在研究中,但再次感谢您的反馈。 祝您搜索顺利。虽然我希望你能找到另一种方法,但我认为当前版本的 WatchKit 将限制你使用这些技术。【参考方案2】:

我的研究结论是,根据 Apple 当前版本的 WatchKit 文档,您将需要 2 个计时器:Apple Watch 上的 WK 计时器和 iPhone 上的 NSTimer。 2 个定时器应该同步启动/触发,WK 定时器在唤醒/睡眠模式下继续倒计时,NSTimer 的工作是在定时器结束时发出警报/发送通知。

要使两个计时器保持同步,您需要在用户启动 Apple Watch WK 计时器时立即触发 iPhone NSTimer。

【讨论】:

这对 WatchOS2 仍然有效吗?【参考方案3】:

当 Apple Watch 屏幕变为空白时,它会使应用进入睡眠状态并暂停您已启动的 Timer(s),直到应用重新回到前台。 这在模拟器中不会发生,但会在真实设备上引起问题。 但是,WKInterfaceTimer 不受影响,因为它基于未来日期并在内部处理。

因此,要在应用返回前台后保持计时器同步,只需比较 Timer 块中的两个日期并观察这两个日期之间的差异即可。

在以下示例中,如果您只想更新计时器并知道倒计时何时完成,那么以下内容就足够了:

//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()
        
    


但是……

WKInterfaceTimerTimer 将在几毫秒内不同步,因此如果您想在 WKInterfaceTimer 计数更新的同时更新 UI,那么上面的逻辑是不够的。 就我而言,我想更新图像;就像环形动画一样,我解决它的唯一方法是将WKInterfaceTimer 转储为WKInterfaceLabel + 一个WKInterfaceGroup 并在计时器块中手动更新标签和组的背景图像。

自定义解决方案:

//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:我试图找到一个优雅的解决方案来解决所有这些问题,但找不到一个现成的例子,所以我分享了我最终得到的结果。

希望对某人有所帮助 :)

【讨论】:

以上是关于Apple Watch 显示器睡眠暂停 NSTimer,但 WKInterfaceTimer 一直倒计时的主要内容,如果未能解决你的问题,请参考以下文章

如何以编程方式禁用 Apple Watch 的睡眠模式

如何在 Apple Watch 上禁用“转入睡眠”?

Apple Watch Series 6或将增加焦虑监测和睡眠追踪功能

3日 | SteamVR将放弃支持苹果macOS系统;Apple Watch Series 6或将增加焦虑监测和睡眠追踪功能

黑苹果上用Apple watch 解锁 MacBook

Apple Watch 让应用程序保持活跃几秒钟