Я изо всех сил пытался понять это в течение нескольких дней: мне нужно создать таймер, который пользователь не может убить, поэтому, как только они запустят его, даже если они убьют приложение или оно войдет в фоновый режим, он подберет, где он остановился, и для этого я сохраняю дату завершения работы приложения, а затем вычисляю разницу, так что эта часть работает нормально.
Однако проблема, с которой я продолжаю сталкиваться, заключается в том, что второй таймер, похоже, запускается, если я сворачиваю приложение и возвращаю его обратно. Я не уверен, как таймеры управляются в фоновом режиме, но ничего из того, что я пробовал (вызов timer.invalidate() при вызове applicationWillResignActive, вызов его в 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() }
}
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) {
наtimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) {
31.07.2019