简体   繁体   中英

How to pass a Timer from Second View Controller to first?

So I have a timer that saves that saves the ending time using NSUserDefaults but I want to push that timer to the previous ViewController as well. The timer should be started on the second View Controller, and if you go back, or exit the app and reopen it, the timer should display. I have an idea of how do it with a Singleton DataService, but not quite sure how to put it all together. Here is my code as of now.

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

}

Any help would be much appreciated. If you need any clarification, please let me know.

You are juggling several issues: Passing an object between view controllers, triggering code at some future time, and having a timer persist in the background.

As far as a timer that runs while your program is in the background, you can simply calculate the number of seconds between now and your target time and set a non-repeating timer for that number of seconds in the future. There's no reason to fire a repeating timer every second and do math to see if your time has passed yet. The way you're doing it will run the CPU hotter and drain your battery faster, so better to set up a single timer in the future.

Next, dealing with timers while in the background. The short answer is that you can't. Apps don't actually run in the background for very long. They quickly get suspended, which is a state where they are not getting any CPU time at all. You can ask for background time, but the system limits you to 3 minutes. You can play tricks to get more than 3 minutes of background time, but those tricks will cause Apple to reject your app, and would drain down the user's battery quite quickly if you did manage to sneak it by Apple. (When an app is running in the background the phone isn't able to go to sleep. THE CPU stays fully powered up, drawing a LOT more current than it does in the sleep state.

Finally, passing your timer from one view controller to the next. Yes, you can certainly use a singleton to make the timer a shared resource. You could also set up your two view controllers so that in the code that invokes the second from the first, you give the second view controller a delegate pointer back to the first, and set up a protocol that would let you pass the timer from the second view controller back to the first.

However, a timer calls a single target, so while you'll have access to the timer from either view controller using either the singleton pattern or the delegate pattern, the timer will still call the original target that you used when you set it up.

You could make your singleton the target of the timer, give the singleton a delegate, and have the singleton send a message to it's delegate when the timer fires. Then when you switch view controllers you could change the singleton's delegate to point to the new view controller.

Alternately you could record the "fire date" of your timer in your viewWillDisappear method, invalidate the timer, and create a new timer with that same fire date (actually you'd have to do some math to convert a fire date to a number of seconds, but it would be a single call.)

You could also use a local notification with a future fire date and set it up to play a sound. However, that won't invoke your program from the background unless the user responds to the notification.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM