所以我有一个保存的计时器,使用NSUserDefaults保存结束时间,但我也希望将该计时器推送到之前的ViewController。应该在第二个View Controller上启动计时器,如果您返回,或退出应用程序并重新打开它,则应显示计时器。我知道如何使用Singleton DataService,但不太确定如何将它们放在一起。这是我现在的代码。
import UIKit
import UserNotifications
let stopTimeKey = "stopTimeKey"
class QOTDVC: UIViewController {
// TIMER VARIABLES
let timeInterval: Double = 89893
let defaults = UserDefaults.standard
var expirationDate = NSDate()
@IBOutlet weak var timerLabel: UILabel!
@IBAction func DoneWithQuestion(_ sender: AnyObject) {
self.dismiss(animated: true, completion: nil)
}
@IBOutlet weak var timerCounter: UILabel!
var timer: Timer?
var stopTime: Date?
override func viewDidLoad() {
super.viewDidLoad()
saveStopTime()
NotificationCenter.default.addObserver(self, selector: #selector(saveStopTime), name: NSNotification.Name(rawValue: "Date") , object: nil)
}
func alert(message: String, title: String = "") {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
let OKAction = UIAlertAction(title: "OK", style: .default) {
UIAlertAction in
self.registerForLocalNotifications()
StartTimerInitiated()
}
alertController.addAction(OKAction)
self.present(alertController, animated: true, completion: nil)
}
func registerForLocalNotifications() {
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .sound]) { (granted, error) in
if granted {
let content = UNMutableNotificationContent()
content.title = "Ready for the QOTD"
content.body = "You have 30 seconds to answer the question"
content.sound = UNNotificationSound.default()
let trigger = UNTimeIntervalNotificationTrigger.init(timeInterval: self.timeInterval , repeats: false)
let request = UNNotificationRequest(identifier: "myTrigger", content: content, trigger: trigger)
center.add(request)
}
}
}
func StartTimerInitiated() {
let time = Date(timeIntervalSinceNow: timeInterval)
if time.compare(Date()) == .orderedDescending {
startTimer(stopTime: time)
} else {
timerLabel.text = "timer date must be in future"
}
}
// MARK: Timer stuff
func startTimer(stopTime: Date) {
// save `stopTime` in case app is terminated
UserDefaults.standard.set(stopTime, forKey: stopTimeKey)
self.stopTime = stopTime
// start NSTimer
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(QOTDVC.handleTimer), userInfo: nil, repeats: true)
// start local notification (so we're notified if timer expires while app is not running)
}
func stopTimer() {
timer?.invalidate()
timer = nil
}
let dateComponentsFormatter: DateComponentsFormatter = {
let _formatter = DateComponentsFormatter()
_formatter.allowedUnits = [.hour, .minute, .second]
_formatter.unitsStyle = .positional
_formatter.zeroFormattingBehavior = .pad
return _formatter
}()
func handleTimer(timer: Timer) {
let now = Date()
if stopTime!.compare(now) == .orderedDescending {
timerLabel.text = dateComponentsFormatter.string(from: now, to: stopTime!)
} else {
stopTimer()
notifyTimerCompleted()
}
}
func notifyTimerCompleted() {
timerLabel.text = "Timer done!"
}
func saveStopTime() {
stopTime = UserDefaults.standard.object(forKey: stopTimeKey) as? Date
if let time = stopTime {
if time.compare(Date()) == .orderedDescending {
startTimer(stopTime: time)
} else {
notifyTimerCompleted()
}
}
stopTime = UserDefaults.standard.object(forKey: stopTimeKey) as? Date
}
非常感谢任何帮助。如果您需要任何澄清,请告诉我。
答案 0 :(得分:0)
您正在处理几个问题:在视图控制器之间传递对象,在将来某个时间触发代码,并在后台保留一个计时器。
对于在程序在后台运行的计时器,您可以简单地计算从现在到目标时间之间的秒数,并在将来为该秒数设置非重复计时器。没有理由每秒发射一次重复计时器并做数学计算以确定你的时间是否过去了。你这样做会让CPU运行得更热,并且你的电池耗电更快,所以最好在将来设置一个计时器。
接下来,在后台处理计时器。简短的回答是,你做不到。应用程序实际上并不在后台运行很长时间。它们很快就会被暂停,这是一个他们根本没有获得任何CPU时间的状态。您可以询问后台时间,但系统会将您限制为3分钟。您可以通过技巧获得超过3分钟的后台时间,但这些技巧将导致Apple拒绝您的应用程序,并且如果您设法通过Apple偷偷用它,将很快耗尽用户的电池电量。 (当应用程序在后台运行时,手机无法进入休眠状态.CPU完全上电,比睡眠状态下的电流更多。
最后,将计时器从一个视图控制器传递到下一个视图控制器。是的,您当然可以使用单例来使计时器成为共享资源。您还可以设置两个视图控制器,以便在从第一个调用第二个视图控制器的代码中,为第二个视图控制器提供一个委托指针,返回第一个,并设置一个允许您从第二个视图控制器回到第一个。
但是,计时器会调用单个目标,因此当您使用单例模式或委托模式从任一视图控制器访问计时器时,计时器仍将调用您在设置时使用的原始目标它了。
你可以让你的单身人士成为计时器的目标,给单身人士一个委托,并让单身人士在计时器开火时向其代表发送信息。然后,当您切换视图控制器时,您可以将单例的委托更改为指向新的视图控制器。
或者,您可以在viewWillDisappear方法中记录计时器的“开火日期”,使计时器无效,并创建一个具有相同开火日期的新计时器(实际上,您必须进行一些数学计算才能将开火日期转换为几秒钟,但这只是一个电话。)
您还可以使用具有未来开火日期的本地通知,并将其设置为播放声音。但是,除非用户响应通知,否则不会从后台调用您的程序。