【发布时间】:2019-07-30 21:23:54
【问题描述】:
这几天我一直在努力解决这个问题:我需要创建一个用户无法杀死的计时器,所以一旦他们启动它,即使他们杀死应用程序或它进入后台,它也会从中断的地方继续,为了实现这一点,我保存了应用程序终止的日期,然后计算差异,以便该部分正常工作。
然而,我一直遇到的问题是,如果我最小化应用程序并将其恢复,第二个计时器似乎会启动。我不确定如何在后台管理计时器,但我尝试的任何方法(在 applicationWillResignActive 被调用时调用 timer.invalidate() ,在 deinit() 中调用它)似乎都不起作用。我在此之后看到的行为是计时器将像这样计数:80 - 78 - 79 - 76 - 77..
还有一个问题是计时器在终止应用程序后有时会超过它应该运行的时间,但我找不到确切的原因,因为它并不总是发生。
知道我做错了什么吗?
非常感谢。
class Focus: UIViewController {
// MARK: Variables
var timer = Timer()
let timeToFocus = UserDefaults.standard.double(forKey: "UDTimeToFocus")
let currentFocusedStats = UserDefaults.standard.integer(forKey: "UDFocusStats")
// MARK: Outlets
@IBOutlet weak var progress: KDCircularProgress!
@IBOutlet weak var timeLabel: UILabel!
@IBOutlet weak var focusTimeLabel: UILabel!
@IBOutlet weak var stepNameLabel: UILabel!
@IBOutlet weak var focusAgain: UIButton!
@IBOutlet weak var allDone: UIButton!
@IBOutlet weak var help: UIButton!
@IBOutlet weak var dottedCircle: UIImageView!
// MARK: Outlet Functions
@IBAction func helpTU(_ sender: Any) { performSegue(withIdentifier: "ToFocusingHelp", sender: nil) }
@IBAction func helpTD(_ sender: Any) { help.tap(shape: .rectangle) }
@IBAction func allDoneTU(_ sender: Any) {
UserDefaults.standard.set(false, forKey: "UDFocusIsRunning")
UserDefaults.standard.set(false, forKey: "UDShouldStartFocus")
completeSession()
hero(destination: "List", type: .zoomOut)
}
@IBAction func allDoneTD(_ sender: Any) { allDone.tap(shape: .rectangle) }
@IBAction func focusAgainTU(_ sender: Any) {
UserDefaults.standard.set(currentFocusedStats + Int(timeToFocus), forKey: "UDFocusStats")
UserDefaults.standard.set(true, forKey: "UDShouldStartFocus")
initFocus()
}
@IBAction func focusAgainTD(_ sender: Any) { focusAgain.tap(shape: .rectangle) }
// MARK: Class Functions
@objc func initFocus() {
var ticker = 0.0
var angle = 0.0
var duration = 0.0
if UserDefaults.standard.bool(forKey: "UDShouldStartFocus") == true {
UserDefaults.standard.set(Date(), forKey: "UDFocusStartDate")
UserDefaults.standard.set(false, forKey: "UDShouldStartFocus")
ticker = timeToFocus
duration = timeToFocus
angle = 0.0
print("starting")
} else {
let elapsedTime = difference(between: UserDefaults.standard.object(forKey: "UDFocusStartDate") as! Date, and: Date())
let timeLeft = timeToFocus - elapsedTime
ticker = timeLeft
duration = timeLeft
angle = elapsedTime / (timeToFocus / 360)
}
// Timer
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
if ticker > 0 {
self.timeLabel.text = "\(Int(ticker))s"
ticker -= 1
}
}
timer.fire()
// Progress Circle
progress.animate(fromAngle: angle, toAngle: 360, duration: duration) { completed in
if completed { self.completeSession() }
}
// UI Changes
allDone.isHidden = true
focusAgain.isHidden = true
help.isHidden = false
}
func completeSession() {
// The timer gets fired every time, but this will invalidate it if it's complete
timer.invalidate()
timeLabel.text = "Done"
help.isHidden = true
allDone.isHidden = false
focusAgain.isHidden = false
}
// MARK: viewDidLoad
override func viewDidLoad() {
initFocus()
allDone.isHidden = true
focusAgain.isHidden = true
if timeToFocus < 3600 { focusTimeLabel.text = "Focusing for \(Int(timeToFocus/60)) minutes" }
else if timeToFocus == 3600 { focusTimeLabel.text = "Focusing for \(Int(timeToFocus/60/60)) hour" }
else { focusTimeLabel.text = "Focusing for \(Int(timeToFocus/60/60)) hours" }
stepNameLabel.text = UserDefaults.standard.string(forKey: "UDSelectedStep")
// This resumes the timer when the user sent the app in the background.
NotificationCenter.default.addObserver(self, selector: #selector(self.initFocus), name: NSNotification.Name(rawValue: "WillEnterForeground"), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.fadeProgress), name: NSNotification.Name(rawValue: "WillEnterForeground"), object: nil)
}
@objc func fadeProgress(){
// This function is called both when the view will enter foreground (for waking the phone or switching from another app) and on viewWillAppear (for starting the app fresh). It will fade the progress circle and buttons to hide a flicker that occurs.
timeLabel.alpha = 0
dottedCircle.alpha = 0
progress.alpha = 0
allDone.alpha = 0
focusAgain.alpha = 0
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2, execute: {
UIButton.animate(withDuration: 0.5, animations: {
self.timeLabel.alpha = 1
self.dottedCircle.alpha = 1
self.progress.alpha = 1
self.allDone.alpha = 1
self.focusAgain.alpha = 1
})
})
}
// MARK: viewWillAppear
override func viewWillAppear(_ animated: Bool) { fadeProgress() }
}
【问题讨论】: