NEW: My new book Pro SwiftUI is out now – level up your SwiftUI skills today! >>

SOLVED: How to make timer continue working in background ?

Forums > SwiftUI

@X901  

I tried this post without add isActive https://www.hackingwithswift.com/books/ios-swiftui/counting-down-with-a-timer when I go to background it only work for 3 sec then stop I want to create StopWatch, so it should be working on background as on Clock app but for unknown reason it won't work in background

1      

hi Basel,

a typical way to handle this is to keep track of a variable totalAccumulatedTime: TimeInterval. and also, keep track of the date you last checked the status of the timer, say lastDateObserved: Date

  • when you start the timer, set totalAccumulatedTime = 0 and set lastDateObserved to the current date
  • when your timer fires, compute the time elapsed between now and when you last checked the date and update both variables
    let currentDate = Date()
    let currentAccumulatedTime = currentDate.timeIntervalSince(lastDateObserved)
    totalAccumulatedTime +=  currentAccumulatedTime
    lastDateObserved = currentDate
  • do the same update when you enter the background (but shut down the timer)
  • do the same update when the app becomes active (but restart the timer)

for the record, if your timer is set to fire every second, it may not fire exactly every second. the UI might be busy, so things may be delayed a little bit (although it may not be noticeable in practice). measuring relative time accumulated is perhaps a little more accurate than simply adding one second each time the timer fires.

hope that helps,

DMG

2      

Hi Basel,

I had this issue with WatchOS whose apps are going to the background whenever you do a wrist down (which is kind of annoying) I solved this problem like this:

I created a new Timer class. The SwiftUI Timer freezes and slows down on WatchOS, therefore I used a custom one. You can use the SwiftUI one if you want as you are using iOS

File StopWatchManager.swift


import SwiftUI

class StopWatchManager: ObservableObject {
    @Published var secondsElapsed: Int = 0
    var timer: Timer = Timer()

    func start() {
        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
            self.secondsElapsed += 1
        }
    }

    func stop() {
        timer.invalidate()
        secondsElapsed = 0
    }

    func pause() {
        timer.invalidate()
    }
}

Then you have to pause the timer whenever your app goes to the background and restart it whenever your app is going to the foreground. You can do it like this:

import UserNotifications

Add a notificationDate state:

@State private var notificationDate: Date = Date()

Create 1 function to request persmission for UserNotifications

func requestPermission() {
    UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in
        if success {
            print("All set!")
        } else if let error = error {
            print(error.localizedDescription)
        }
    }
}

Add 2 custom functions for dealing with moving between background and foreground:

// MARK: - Custom User Notifications

    func movingToBackground() {
        print("Moving to the background")
        notificationDate = Date()
        stopWatchManager.pause()
    }

    func movingToForeground() {
        print("Moving to the foreground")
        let deltaTime: Int = Int(Date().timeIntervalSince(notificationDate))
        stopWatchManager.secondsElapsed += deltaTime
        stopWatchManager.start()
    }

Add this modifier to the view for requesting permission:

.onAppear(perform: requestPermission)

Finally, add those 2 modifiers for calling the right function at the right time. I used WKExtension notifications for watchOS but you can use the iOS ones:

.onReceive(NotificationCenter.default.publisher(
                    for: WKExtension.applicationWillResignActiveNotification
        )) { _ in
            movingToBackground()
        }
.onReceive(NotificationCenter.default.publisher(
                    for: WKExtension.applicationDidBecomeActiveNotification
        )) { _ in
            movingToForeground()
        }

This setup works perfectly fine on WatchOS, therefore it should work even better on iOS

Let me know if it solves your problem

3      

@X901  

Thank you both of you

1      

I have a question about this issue too. Lets say you would like to create a countdown timer and give at certain intervals an audio command. How would you solve that while you app ins in background? For some reason it is just working on the simulator.

1      

@alexismoulin How to notify about the end of the timer after 5 seconds while screen is off. I try to use UserNotifications but it fires when the app is closed only.

1      

1      

Hacking with Swift is sponsored by Play

SPONSORED Play is the first native iOS design tool created for designers and engineers. You can install Play for iOS and iPad today and sign up to check out the Beta of our macOS app with SwiftUI code export. We're also hiring engineers!

Click to learn more about Play!

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.