首页 > 解决方案 > Swift:当应用程序进入后台时启动另一个计时器

问题描述

几天来我一直在努力解决这个问题:我需要创建一个用户无法杀死的计时器,所以一旦他们启动它,即使他们杀死应用程序或它进入后台,它也会在哪里找到它停止了,为了实现这一点,我正在保存应用程序终止的日期,然后计算差异,以便该部分正常工作。

然而,我一直遇到的问题是,如果我最小化应用程序并将其恢复,第二个计时器似乎会启动。我不确定如何在后台管理计时器,但我尝试的任何方法(在 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() }
}

标签: swifttimer

解决方案


似乎问题在于您在内部创建了一个局部timer变量,initFocus()但您在内部调用invalidatecompleteSession另一个timer在那里定义的变量:

class Focus: UIViewController {

// MARK: Variables
var timer = Timer()

推荐阅读