当应用程序不是焦点时,运行一个在后台倒数的计时器吗?迅速 [英] Running a Timer that counts down in background when app isnt the focus? Swift

查看:79
本文介绍了当应用程序不是焦点时,运行一个在后台倒数的计时器吗?迅速的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我希望我的倒数计时器暂停运行,然后在应用离开/返回焦点时恢复计时,利用这段时间计算应该减去多少时间.

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆