手机休眠时运行计时器

时间:2020-05-14 18:25:57

标签: ios swift xcode timer sleep

我正在构建一个应用程序,并且如果用户将屏幕发送到后台,或者如果他们将手机置于睡眠状态并再次打开它,则需要一个计时器来运行。我需要计时器才能继续。

我尝试记录退出并再次输入的时间,将两者相减并加到运行计数中,这在Xcode模拟器上似乎可以正常工作,但是当我在手机上运行时却无法工作。有任何想法吗?

以下是参考代码。
计时器从一个按钮开始,我没有包含该部分,但这只是一个简单的IBAction,它调用了timer.fire()函数。

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

}

1 个答案:

答案 0 :(得分:1)

您确实有正确的主意,但我看到的第一个问题是viewWillDissapear仅在您离开视图控制器转到新的viewController时才调用-当应用程序离开视图进入时不调用背景(按下主屏幕按钮)

我相信您正在寻找的回调函数是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

  • 还要确保在创建新项目时在项目中启用CoreData *然后在xcdatamodel文件中设置实体和属性:

CoreData setup

希望这会有所帮助