在手机休眠时运行计时器
Posted
技术标签:
【中文标题】在手机休眠时运行计时器【英文标题】:Running a timer when the phone is sleeping 【发布时间】:2020-08-31 10:06:13 【问题描述】:我正在构建一个应用程序,如果用户将屏幕发送到后台,或者如果他们让手机进入睡眠状态并再次打开它,我需要一个计时器来运行。我需要计时器继续运行。
我尝试记录退出并再次输入的时间,减去两者并将其添加到运行计数中,它似乎在 Xcode 模拟器上运行良好,但当我在手机上运行它时却没有工作。有任何想法吗?
这是供参考的代码。 计时器从一个按钮开始,我没有包含该部分,但它只是一个调用 timer.fire() 函数的简单 IBAction。
var time = 0.0
var timer = Timer()
var exitTime : Double = 0
var resumeTime : Double = 0
override func viewWillDisappear(_ animated: Bool)
super.viewWillDisappear(true)
exitTime = Date().timeIntervalSinceNow
override func awakeFromNib()
super.awakeFromNib()
resumeTime = Date().timeIntervalSinceNow
time += (resumeTime-exitTime)
timer.fire()
func startTimer()
if !isTimeRunning
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector:
#selector(WorkoutStartedViewController.action), userInfo: nil, repeats: true)
isTimeRunning = true
func pauseTimer()
timer.invalidate()
isTimeRunning = false
@objc func action()
time += 0.1
timerLabel.text = String(time)
let floorCounter = Int(floor(time))
let hour = floorCounter/3600
let minute = (floorCounter % 3600)/60
var minuteString = "\(minute)"
if minute < 10
minuteString = "0\(minute)"
let second = (floorCounter % 3600) % 60
var secondString = "\(second)"
if second < 10
secondString = "0\(second)"
if time < 3600.0
timerLabel.text = "\(minuteString):\(secondString)"
else
timerLabel.text = "\(hour):\(minuteString):\(secondString)"
【问题讨论】:
您说“我尝试记录退出并再次输入的时间,减去两者并将其添加到运行计数中......当我在手机上运行它时它不起作用。”您发布的代码没有这样做。它使用计时器来增加计数。不要那样做。开始计时器时,将当前 (date()
) 保存到 UserDefaults。再次输入函数时,从当前日期中减去保存的日期。这将是自计时器启动以来的秒数,无论您的计时器方法是否一直被调用。
【参考方案1】:
您确实有正确的想法,但我看到的第一个问题是 viewWillDissapear
仅在您离开视图控制器以转到新的视图控制器时调用 - 当应用程序离开视图进入后台时不会调用它(主页按钮按下)
我相信您正在寻找的回调函数是UIApplication.willResignActive
(进入后台)和UIApplication.didBecomeActive
(应用重新打开)
您可以在AppDelegate
中访问这些方法,或者您可以在视图控制器上设置它们,在这里混合您的代码和一些更改以在一个初始 VC 上生成一个工作示例:
import UIKit
import CoreData
class ViewController: UIViewController
@IBOutlet weak var timerLabel: UILabel!
var time = 0.0
var timer = Timer()
var exitTime : Date? // Change to Date
var resumeTime : Date? // Change to Date
var isTimeRunning = false
override func viewDidLoad()
super.viewDidLoad()
// Do any additional setup after loading the view.
startTimer()
override func viewWillAppear(_ animated: Bool)
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self,
selector: #selector(applicationDidBecomeActive),
name: UIApplication.didBecomeActiveNotification,
object: nil)
// Add willResign observer
NotificationCenter.default.addObserver(self,
selector: #selector(applicationWillResign),
name: UIApplication.willResignActiveNotification,
object: nil)
override func viewWillDisappear(_ animated: Bool)
// Remove becomeActive observer
NotificationCenter.default.removeObserver(self,
name: UIApplication.didBecomeActiveNotification,
object: nil)
// Remove becomeActive observer
NotificationCenter.default.removeObserver(self,
name: UIApplication.willResignActiveNotification,
object: nil)
func startTimer()
if !isTimeRunning
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector:
#selector(self.action), userInfo: nil, repeats: true)
isTimeRunning = true
@objc func action()
time += 0.1
timerLabel.text = String(time)
let floorCounter = Int(floor(time))
let hour = floorCounter/3600
let minute = (floorCounter % 3600)/60
var minuteString = "\(minute)"
if minute < 10
minuteString = "0\(minute)"
let second = (floorCounter % 3600) % 60
var secondString = "\(second)"
if second < 10
secondString = "0\(second)"
if time < 3600.0
timerLabel.text = "\(minuteString):\(secondString)"
else
timerLabel.text = "\(hour):\(minuteString):\(secondString)"
@objc func applicationDidBecomeActive()
// handle event
lookForActiveTimers()
func lookForActiveTimers()
var timers = [NSManagedObject]()
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else
return
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Timers")
//3
do
timers = try managedContext.fetch(fetchRequest)
print("timers: \(timers)")
var activeTimer: NSManagedObject?
for timer in timers
if let active = timer.value(forKey: "active") as? Bool
if active
activeTimer = timer
if let activeTimer = activeTimer
// Handle active timer (may need to go to a new view)
if let closeDate = activeTimer.value(forKey: "appCloseTime") as? Date
if let alreadyTimed = activeTimer.value(forKey: "alreadyTimed") as? Double
let now = Date()
let difference = now.timeIntervalSince(closeDate)
// Handle set up again here
print("App opened with a difference of \(difference) and already ran for a total of \(alreadyTimed) seconds before close")
time = alreadyTimed + difference
startTimer()
else
print("We dont have any active timers")
// Remove active timers because we reset them up
for timer in timers
managedContext.delete(timer)
do
print("deleted")
try managedContext.save() // <- remember to put this :)
catch
// Do something... fatalerror
catch let error as NSError
print("Could not fetch. \(error), \(error.userInfo)")
@objc func applicationWillResign()
// handle event
saveActiveTimer()
func saveActiveTimer()
if isTimeRunning
// Create a new alarm object
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else
return
let context = appDelegate.persistentContainer.viewContext
if let entity = NSEntityDescription.entity(forEntityName: "Timers", in: context)
let newTimer = NSManagedObject(entity: entity, insertInto: context)
newTimer.setValue(true, forKey: "active")
let now = Date()
newTimer.setValue(now, forKey: "appCloseTime")
newTimer.setValue(self.time, forKey: "alreadyTimed")
do
try context.save()
print("object saved success")
catch
print("Failed saving")
编辑 - 这是在 xCode 11.3 和物理设备 ios 13.2 上经过完整测试和工作的代码 - 您必须弄清楚如何根据您的按钮启动和停止计时器 - 但此示例只是在应用程序时启动计时器首次打开并且永远不会停止或重置它。
您可以通过创建一个新的单视图 xCode 项目并用上面的代码替换它为您创建的第一个视图控制器中的代码来重现这一点。然后创建一个标签以附加到 VC 上的插座timerLabel
希望对你有帮助
【讨论】:
我添加了该代码,效果很好。我将我在参考代码中的 exitTime 和 resumeTime 内容添加到您编写的函数中。使用模拟器,即使设备进入睡眠状态,它也能完美运行,但当我在手机上运行它时,它又无法正常工作。是否有特殊原因或需要打开某些权限? 当你说它不起作用时——它是崩溃、不调用方法/做任何事情,还是没有正确更新计时器? - 你的项目部署目标操作系统是什么? - 你的物理设备操作系统呢? 我将代码更新为在 xCode 11.3 和运行 iOS 13.2 的物理设备上测试的完整工作示例 vc 它只是不会更新时间。基本上,如果我在 10 秒时关闭应用程序并在一分钟后打开它,它将从 10 秒恢复。但是,更新后的代码起到了作用,非常感谢您的帮助! 没问题很高兴我能帮上忙!在代码中,您可以看到我们必须存储的两个重要内容是已计时的时间量('alreadyTimed' - 您指的是 10 秒)和应用程序关闭的日期('appCloseTime')-> 然后当它重新打开时,我们计算上次打开和现在之间的差异,并将其添加到“alreadyTimed”的数量中以获取计时器的当前时间。 CoreData 还允许它持久化应用程序终止以上是关于在手机休眠时运行计时器的主要内容,如果未能解决你的问题,请参考以下文章