当应用程序不是焦点时,运行一个在后台倒数的计时器吗?迅速 [英] Running a Timer that counts down in background when app isnt the focus? Swift
问题描述
我希望我的倒数计时器暂停运行,然后在应用离开/返回焦点时恢复计时,利用这段时间计算应该减去多少时间.
I want my countdown timer to suspend and then resume when the app leaves / returns to focus, using the time away to calculate how much time should be deducted.
我正在使用应用程序委托文件(不确定这是正确的位置吗?或者是否应将它们作为自己的功能存在于视图控制器文件中?)
I am using the app delegate file (not sure thats the right location? or if they are meant to be in the view controllers file as functions of their own?)
问题是即时消息,例如:
Issue is im getting a lot of errors such as:
"AppDelegate"类型的值没有成员"restTimer"
Value of type 'AppDelegate' has no member 'restTimer'
使用未解决的标识符'nextFireDate'
Use of unresolved identifier 'nextFireDate'
使用未解决的标识符选择器"
Use of unresolved identifier 'selector'
restTimer在我的视图控制器文件中被声明为计时器,但是当我在该文件中尝试这些块时,未解析的标识符出现了相同数量的错误
restTimer was declared as a timer in my view controllers file but when i tried these blocks in that file i got an equal number of errors for unresolved identifiers
并使用以下2个代码块
func applicationWillResignActive(_ application: UIApplication) {
guard let t = self.restTimer else { return }
nextFireDate = t.fireDate
t.invalidate()
和
func applicationDidBecomeActive(_ application: UIApplication) {
guard let n = nextFireDate else { return }
let howMuchLonger = n.timeIntervalSinceDate(NSDate())
if howMuchLonger < 0 {
print("Should have already fired \(howMuchLonger) seconds ago")
target!.performSelector(selector!)
} else {
print("should fire in \(howMuchLonger) seconds")
Timer.scheduledTimerWithTimeInterval(howMuchLonger, target: target!, selector: selector!, userInfo: nil, repeats: false)
}
}
更新:由于合并了答案的问题,添加了完整视图代码
UPDATE: Added full views code due to issue incorporating the answer
import Foundation
import UIKit
class RestController: UIViewController {
@IBOutlet weak var restRemainingCountdownLabel: UILabel!
@IBOutlet weak var setsRemainingCountdownLabel: UILabel!
@IBOutlet weak var numberOfSetsLabel: UILabel!
@IBOutlet weak var numberOfRestLabel: UILabel!
@IBOutlet weak var adjustSetsStepper: UIStepper!
@IBOutlet weak var adjustRestStepper: UIStepper!
var startDate: Date!
let startDateKey = "start.date"
let interval = TimeInterval(20)
var restTimer: Timer!
var restCount = 0
var setCount = 0
var selectedTime = 1
var selectedSets = 1
private let resignDateKey = "resign.date"
@IBAction func endSetPressed(_ sender: Any) {
if (setCount > 0){
setCount -= 1
setsRemainingCountdownLabel.text = String(setCount)
}
handleTimer()
}
@IBAction func setStepperValueChanged(_ sender: UIStepper) {
numberOfSetsLabel.text = Int(sender.value).description
self.setCount = Int(sender.value)
self.selectedSets = setCount
setsRemainingCountdownLabel.text = String(setCount)
}
@IBAction func restStepperValueChanged(_ sender: UIStepper) {
numberOfRestLabel.text = Int(sender.value).description
let timeMinSec = timeFormatted(totalSeconds: Int(sender.value)*60)
restRemainingCountdownLabel.text = timeMinSec
self.selectedTime = Int(sender.value)
restCount = self.selectedTime * 60
}
@IBAction func resetSetsButton(_ sender: Any) {
setCount = Int(adjustSetsStepper.value)
setsRemainingCountdownLabel.text = String(setCount)
}
override func viewDidLoad() {
super.viewDidLoad()
numberOfSetsLabel.text = String(selectedSets)
numberOfRestLabel.text = String(selectedTime)
createTimer(interval: interval)
NotificationCenter.default.addObserver(self, selector: #selector(willResignActive), name: NSNotification.Name.UIApplicationWillResignActive, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive), name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
@objc private func willResignActive(notification: Notification) {
print("resigning")
guard restTimer.isValid else {
UserDefaults.standard.removeObject(forKey: startDateKey)
return
}
restTimer.invalidate()
UserDefaults.standard.set(Date(), forKey: startDateKey)
}
@objc private func didBecomeActive(notification: Notification) {
print("resume")
if let startDate = UserDefaults.standard.object(forKey: startDateKey) as? Date {
let elapsed = -startDate.timeIntervalSinceNow
print("elpased time: \(elapsed) remaining time: \(interval - elapsed)")
if elapsed > interval {
timerUp()
} else {
createTimer(interval: interval - elapsed)
}
}
}
private func createTimer (interval: TimeInterval) {
restTimer = Timer.scheduledTimer(withTimeInterval: interval , repeats: false) {[weak self] _ in
self?.timerUp()
}
startDate = Date()
}
private func timerUp() {
print("At least \(interval) seconds has elapsed")
}
func handleSets() {
if (setCount > 0) {
self.restCount = self.selectedTime * 60
}
handleTimer()
}
func handleTimer() {
if (restTimer?.isValid ?? false) {
restTimer?.invalidate()
restTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(RestController.updateTimer), userInfo: nil, repeats: true)
} else {
restTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(RestController.updateTimer), userInfo: nil, repeats: true)
}
}
func updateTimer() {
if (restCount > 0){
restCount -= 1
} else if (restCount == 0){
restTimer?.invalidate()
}
restRemainingCountdownLabel.text = timeFormatted(totalSeconds: restCount)
}
func timeFormatted(totalSeconds: Int) -> String {
let seconds: Int = totalSeconds % 60
let minutes: Int = (totalSeconds / 60) % 60
return String(format: "%02d:%02d", minutes, seconds)
}
推荐答案
您不必为此使用AppDelegate,因为它还会发布通知.您可以根据需要使用AppDelegate.这是使用通知的代码:
You don't have to use the AppDelegate for this because it also posts notifications. You can use the AppDelegate if you want. Here is code using notifications:
class ViewController: UIViewController{
private let startDateKey = "start.date"
private let interval = TimeInterval(20)
private var startDate: Date!
private var timer: Timer!
override func viewDidLoad() {
super.viewDidLoad()
createTimer(interval: interval)
NotificationCenter.default.addObserver(self, selector: #selector(willResignActive), name: NSNotification.Name.UIApplicationWillResignActive, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive), name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
@objc private func willResignActive(notification: Notification) {
print("resigning")
guard timer.isValid else {
UserDefaults.standard.removeObject(forKey: startDateKey)
return
}
timer.invalidate()
UserDefaults.standard.set(Date(), forKey: startDateKey)
}
@objc private func didBecomeActive(notification: Notification) {
print("resume")
if let startDate = UserDefaults.standard.object(forKey: startDateKey) as? Date {
let elapsed = -startDate.timeIntervalSinceNow
print("elpased time: \(elapsed) remaining time: \(interval - elapsed)")
if elapsed > interval {
timerUp()
} else {
createTimer(interval: interval - elapsed)
}
}
}
private func createTimer (interval: TimeInterval) {
timer = Timer.scheduledTimer(withTimeInterval: interval , repeats: false) {[weak self] _ in
self?.timerUp()
}
startDate = Date()
}
private func timerUp() {
print("At least \(interval) seconds has elapsed")
}
}
这篇关于当应用程序不是焦点时,运行一个在后台倒数的计时器吗?迅速的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!