background run timer swift - swift

I want the timer to run even when I close the application. I want it to work in the background counter. the timer goes back one second when I run it.(counter) How can I do that?
class TimerViewController: UIViewController {
var selectedDay: String?
var seconds =
var timer = Timer()
#IBAction func start(_ sender: AnyObject) {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(TimerViewController.counter), userInfo: nil, repeats: true)
sliderOutlet.isHidden = true
startOutlet.isHidden = true
}
#objc func counter() {
seconds -= 1
favoriteDayTextField.text = String(seconds) + " Seconds"
var bgTask = UIBackgroundTaskIdentifier(rawValue: seconds)
bgTask = UIApplication.shared.beginBackgroundTask(expirationHandler: {
UIApplication.shared.endBackgroundTask(bgTask)
})
if (seconds == 0) {
timer.invalidate()
if self.button.isOn {
updateState()
} else {
updateState1()
}
}
}
}

I am not clear what you want to achieve. Suppose you want to update the label after the timer has started each 1 second. Then one approach will be:-
Start the timer in view did load if the duration is remaining.
Register for applicationWillTerminate
In application will terminate save the passed duration and terminated time to calculate remaining time in next launch.
var remainingDuration: TimeInterval!
override func viewDidLoad() {
super.viewDidLoad()
let remainingDurationFromLastLaunch = UserDefaults.standard.object(forKey: "duration") as? TimeInterval ?? 0
let lastTerminatedTime = UserDefaults.standard.object(forKey: "lastTerminatedDate") as? Date ?? Date()
if Date().timeInterval(since: lastTerminatedTime) > remainingDurationFromLastLaunch {
remainingDuration = remainingDurationFromLastLaunch - Date().timeInterval(since: lastTerminatedTime)
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(TimerViewController.counter), userInfo: nil, repeats: true)
NotificationCenter.default.addObserver(self, selector: #selector(TimerViewController.applicationWillTerminate), name: NSNotification.Name.UIApplicationWillTerminate, object: nil)
} else { //Duration is passed....Do whatever you want
}
}
#objc func counter() {
remainingDuration -= 1
if remainingDuration == 0 { //Duration is passed....Do whatever you want
timer.invalidate()
timer = nil
} else {
favoriteDayTextField.text = String(remainingDuration) + " Seconds"
}
}
#objc func applicationWillTerminate() {
if timer != nil {
backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
UserDefaults.standard.set(remainingDuration, forKey: "duration")
UserDefaults.standard.set(Date(), forKey: "lastTerminatedDate")
}
self?.endBackgroundTask()
}
}
func endBackgroundTask() {
UIApplication.shared.endBackgroundTask(backgroundTask)
backgroundTask = UIBackgroundTaskInvalid
}

The only way for your iOS application to perform some action even while it is in the background is to use Background Modes .
However you cannot perform anything and everything while your
application is in background
There are certain limitations to the type of tasks that you can perform . I have attached a really good article for your reference
Background Modes Tutorial
However, I am not sure if you can initiate and continue a timer sort of functionality while your application is in background
Though, keep in mind , once your application is closed (i.e. by double tapping the home button and swiping the application window up to close it completely) , not even Background modes work at that point because the user does not want to run your app anymore, even in the background

Related

Countdown timer not running

Code not running when returning from background. If I suspend the app, close it out completely and re-open it calculates the time perfectly.
How can I get it to run when user leaves the app and returns.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let lastTimerStartTime = UserDefaults.standard.value(forKey: "lastTimerStartTime") as? Date,
let totalTime = UserDefaults.standard.value(forKey: "totalTime") as? Double {
let timeSinceStartTime = Date() - lastTimerStartTime
if timeSinceStartTime < totalTime {
seconds = totalTime - timeSinceStartTime
runTimer()
} else {
timeLabel.text = timeString(time: TimeInterval(seconds))
}
} else {
timeLabel.text = timeString(time: TimeInterval(seconds))
}
}
#IBAction func startBtn(_ sender: Any) {
runTimer()
}
func runTimer() {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(ViewController.updateTimer)), userInfo: nil, repeats: true)
UserDefaults.standard.set(Date(), forKey: "lastTimerStartTime")
UserDefaults.standard.set(seconds, forKey: "totalTime")
}
#objc func updateTimer(){
seconds -= 1
timeLabel.text = timeString(time: TimeInterval(seconds))
}
You need to add code to listen for suspend and resume events. (UIApplication.willResignActiveNotification and UIApplication. didBecomeActiveNotification).
When you get notified that you are going to resign being the active app, you’ll need to save the amount time remaining (or time elapsed) whatever makes sense.
You don’t know if your app will be resumed or killed, so you will need to save the info to UserDefaults, and then pick it up either when handing a didBecomeActiveNotification or when your app gets launched again.
If your app is moved the background and then brought back to the front without being terminated, your viewDidAppear method won’t get called because your view controller’s view never left the screen.

Why does my timer in swift keep speeding up?

I am creating a trivia app in swift and I have a timer that counts down each question. However as the user progresses with each question the timer speeds up. Can someone help me fix this?
My runGameTimer function:
func runGameTimer()
{
gameTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(RockTriviaViewController.updateGameTimer), userInfo: nil, repeats: true)
}
My updateGameTimer function:
#objc func updateGameTimer()
{
gameInt -= 1
timerLabel.text = String(gameInt)
if (gameInt == 0)
{
gameTimer.invalidate()
/*
if (currentQuestion != rockQuestions[questionSet].count)
{
newQuestion()
}
else
{
performSegue(withIdentifier: "showRockScore", sender: self)
}
*/
}
}
Where I call my code:
func newQuestion()
{
gameInt = 11
runGameTimer()
rockQuestion.text = rockQuestions[questionSet][currentQuestion]
rightAnswerPlacement = arc4random_uniform(3)+1
var Button: UIButton = UIButton()
var x = 1
for i in 1...3
{
Button = view.viewWithTag(i) as! UIButton
if(i == Int(rightAnswerPlacement))
{
Button.setTitle(rockAnswers[questionSet][currentQuestion][0], for: .normal)
}
else
{
Button.setTitle(rockAnswers[questionSet][currentQuestion][x], for: .normal)
x = 2
}
}
currentQuestion += 1
}
You're calling runGameTimer() in every call to newQuestion(). If a timer was already running then you'll add a new timer each time, and they will all call your selector. So if you have 3 timers running, your selector will be called 3x as often. That's not what you want.
Change your timer variable to be weak:
weak var gameTimer: Timer?
And then in runGameTimer invalidate the timer before creating a new one, using optional chaining:
func runGameTimer() {
gameTimer?.invalidate() //This will do nothing if gameTimer is nil.
//it will also cause the gameTimer to be nil since it's weak.
gameTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(RockTriviaViewController.updateGameTimer), userInfo: nil, repeats: true)
}
By making the game timer weak it will get set to nil as soon as it's invalidated. (When you schedule a timer the system retains it while it is running so it stays valid as long as it continues to run.)
By using optional chaining to reference the timer:
gameTimer?.invalidate()
The code doesn't do anything if gameTimer is nil.

Convert seconds to Minutes:Seconds in label MacOS

I am a school teacher and we have been 'ordered' to use a specific timer for our lessons, which low and behold doesn't work on our apple iMac's. I am trying to create my own in xcode and so far have created a basic window which will countdown a label in seconds. I have at the moment assigned the buttons and they work (in increments of 60 seconds).
This works and is fine but ideally i would like the label to display minutes and seconds instead (much easier for the kids). What is the best way to code this? Last time i used xcode was in 2009 and i am way out of date now!! Thanks in advance
--
#objc func updateTimer() {
seconds -= 1 //This will decrement(count down)the seconds.
countdownLabel.stringValue = "\(seconds)" //This will update the label.
}
--
#objc func runTimer() {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector:(#selector(ViewController.updateTimer)), userInfo: nil, repeats: true)
}
--
#IBAction func threeMin(_ sender: Any) {
seconds = 180
runTimer()
}
--
There are many solutions. A convenient one is DateComponentsFormatter
let formatter : DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.minute, .second]
return formatter
}()
#objc func updateTimer() {
seconds -= 1
countdownLabel.stringValue = formatter.string(from: TimeInterval(seconds))!
}
Some improvements:
Assign tags to all buttons with their value in seconds for example set the tag of the threeMin button to 180. Then use only one IBAction and connect all buttons to that action.
In the action first check if the timer is running and start it only if it's not running
var timer : Timer?
#IBAction func startTimer(_ sender: NSButton) {
if timer == nil {
seconds = sender.tag
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(ViewController.updateTimer), userInfo: nil, repeats: true)
}
}
Create a function to stop the timer reliably
func stopTimer() {
if timer != nil {
timer?.invalidate()
timer = nil
}
}
In the updateTimer() function stop the timer if seconds is 0
#objc func updateTimer() {
seconds -= 1
countdownLabel.stringValue = formatter.string(from: TimeInterval(seconds))!
if seconds == 0 { stopTimer() }
}

Timer counts instantly instead of respecting interval

Im trying to make a countdown timer. everything works fine except that my timer does not count at regular intervals (1sec); instead it counts all the way down instantly giving me 0 every time. did a lot of search without luck. All examples I could find show similar timeInterval parameter.
var timer = Timer()
var remainingTime = 120
#objc func timerCount () {
if remainingTime > 0 {
while remainingTime > 0 {
remainingTime -= 1
timerLabel.text = String(remainingTime)
print(remainingTime)
}
} else {
timer.invalidate()
}
}
#IBAction func pauseButton(_ sender: Any) {
timer.invalidate()
}
#IBAction func playButton(_ sender: Any) {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.timerCount), userInfo: nil, repeats: true)
}
The reason your code is not working is that you have put an unnecessary while loop in your timerCount() method. You don't need to do this. Your timer will fire this method after each time interval. At very first call this while loop make your remainingTime to 0. This is why you are instantly getting 0 every time.
You just need to remove that while loop.
Can You try like this
var timer = Timer()
var remainingTime = 120
#objc func timerCount () {
let date = NSDate()
let nowdate = UserDefaults.standard.object(forKey: "NowDate") as! Date
let miniute = nowdate.timeIntervalSince(date as Date)
print(Int(miniute))
if (Int(miniute) == 0) {
timer.invalidate()
UserDefaults.standard.removeObject(forKey: "NowDate")
} else {
timerLabel.text = String(Int(miniute))
}
}
#IBAction func pauseButton(_ sender: Any) {
timer.invalidate()
UserDefaults.standard.removeObject(forKey: "NowDate")
}
#IBAction func playButton(_ sender: Any) {
let CurrentDate = NSDate()
let NowDate = CurrentDate.addingTimeInterval(TimeInterval(remainingTime))
UserDefaults.standard.set(NowDate, forKey: "NowDate")
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.timerCount), userInfo: nil, repeats: true)
}

Trying to implement beginBackgroundTaskWithExpirationHandler and UILocalNotification

I have the following code in my AppDelegate for when my application enters the background:
var backgroundUpdateTask: UIBackgroundTaskIdentifier!
func beginBackgroundUpdateTask() {
self.backgroundUpdateTask = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({
self.endBackgroundUpdateTask()
})
}
func endBackgroundUpdateTask() {
UIApplication.sharedApplication().endBackgroundTask(self.backgroundUpdateTask)
self.backgroundUpdateTask = UIBackgroundTaskInvalid
}
func doBackgroundTask() {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
self.beginBackgroundUpdateTask()
// Do something with the result.
var timer = NSTimer.scheduledTimerWithTimeInterval(5, target: self, selector: "displayAlert", userInfo: nil, repeats: false)
NSRunLoop.currentRunLoop().addTimer(timer, forMode: NSDefaultRunLoopMode)
NSRunLoop.currentRunLoop().run()
// End the background task.
self.endBackgroundUpdateTask()
})
}
func displayAlert() {
let note = UILocalNotification()
note.alertBody = "As a test I'm hoping this will run in the background every X number of seconds..."
note.soundName = UILocalNotificationDefaultSoundName
UIApplication.sharedApplication().scheduleLocalNotification(note)
}
func applicationDidEnterBackground(application: UIApplication) {
self.doBackgroundTask()
}
I'm hoping that it executes a UILocalNotification() every X number of seconds specified in the NSTimer.scheduledTimerWithTimeInterval() however it only executes once.
I'm still trying to get my head around how background tasks work. Is there something I'm missing?
In the code sample, the timer you create will only fire once as you have set the "repeats" value to false in the initialiser.