Swift:当应用程序进入后台时启动另一个计时器

Posted

技术标签:

【中文标题】Swift:当应用程序进入后台时启动另一个计时器【英文标题】:Swift: another timer starting when application enters background 【发布时间】:2019-07-30 21:23:54 【问题描述】:

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

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

【问题讨论】:

【参考方案1】:

问题似乎是您在 initFocus() 内创建了一个本地 timer 变量,但您在 completeSession 内调用 invalidate 以获取另一个在那里定义的 timer

class Focus: UIViewController 

// MARK: Variables
var timer = Timer()

【讨论】:

嘿@Vyacheslav,这很有意义,实际上我不知道。 completeSession 在进度完成或按下按钮时被调用,因此当应用程序进入后台时不会使计时器无效。当我收到应用程序将进入后台的通知时,我应该使其失效,还是有更好的方法? let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) 改成timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) 非常感谢您的解释,这似乎已经解决了。

以上是关于Swift:当应用程序进入后台时启动另一个计时器的主要内容,如果未能解决你的问题,请参考以下文章

swift 3:手表应用:如果手表进入睡眠状态,界面控制器之间会有延迟

在swift中,当应用程序进入后台时,如何使用依赖注入保存变量?

当应用程序进入后台和前台时,带有通知中心的登录屏幕,Swift

当应用程序进入和退出后台时更新ios中的计时器

每次应用程序来自android的后台时启动第一个屏幕

当应用程序进入后台时停止后台服务