iOS Timer in The Background

Run timer in background

It's possible through a token that identifies a request to run in the background.

Like this: var bgTask = UIBackgroundTaskIdentifier()

Here is how to use it:

var bgTask = UIBackgroundTaskIdentifier()
bgTask = UIApplication.shared.beginBackgroundTask(expirationHandler: {
UIApplication.shared.endBackgroundTask(bgTask)
})
let timer = Timer.scheduledTimer(timeInterval: 10, target: self, selector: #selector(notificationReceived), userInfo: nil, repeats: true)
RunLoop.current.add(timer, forMode: RunLoopMode.defaultRunLoopMode)

I hope it would be useful!

SwiftUI: How to run a Timer in background

When user leaves the app, it is suspended. One generally doesn’t keep timers going when the user leaves the app. We don't want to kill the user’s battery to update a timer that really isn’t relevant until the user returns to the app.

This obviously means that you do not want to use the “counter” pattern. Instead, capture the the Date when you started the timer, and save it in case the user leaves the app:

func saveStartTime() {
if let startTime = startTime {
UserDefaults.standard.set(startTime, forKey: "startTime")
} else {
UserDefaults.standard.removeObject(forKey: "startTime")
}
}

And, when the app starts, retrieved the saved startTime:

func fetchStartTime() -> Date? {
UserDefaults.standard.object(forKey: "startTime") as? Date
}

And your timer should now not use a counter, but rather calculate the elapsed time between the start time and now:

let now = Date()
let elapsed = now.timeIntervalSince(startTime)

guard elapsed < 15 else {
self.stop()
return
}

self.message = String(format: "%0.1f", elapsed)

Personally, I'd abstract this timer and persistence stuff out of the View:

class Stopwatch: ObservableObject {
/// String to show in UI
@Published private(set) var message = "Not running"

/// Is the timer running?
@Published private(set) var isRunning = false

/// Time that we're counting from
private var startTime: Date? { didSet { saveStartTime() } }

/// The timer
private var timer: AnyCancellable?

init() {
startTime = fetchStartTime()

if startTime != nil {
start()
}
}
}

// MARK: - Public Interface

extension Stopwatch {
func start() {
timer?.cancel() // cancel timer if any

if startTime == nil {
startTime = Date()
}

message = ""

timer = Timer
.publish(every: 0.1, on: .main, in: .common)
.autoconnect()
.sink { [weak self] _ in
guard
let self = self,
let startTime = self.startTime
else { return }

let now = Date()
let elapsed = now.timeIntervalSince(startTime)

guard elapsed < 60 else {
self.stop()
return
}

self.message = String(format: "%0.1f", elapsed)
}

isRunning = true
}

func stop() {
timer?.cancel()
timer = nil
startTime = nil
isRunning = false
message = "Not running"
}
}

// MARK: - Private implementation

private extension Stopwatch {
func saveStartTime() {
if let startTime = startTime {
UserDefaults.standard.set(startTime, forKey: "startTime")
} else {
UserDefaults.standard.removeObject(forKey: "startTime")
}
}

func fetchStartTime() -> Date? {
UserDefaults.standard.object(forKey: "startTime") as? Date
}
}

Then the view can just use this Stopwatch:

struct ContentView: View {
@ObservedObject var stopwatch = Stopwatch()

var body: some View {
VStack {
Text(stopwatch.message)
Button(stopwatch.isRunning ? "Stop" : "Start") {
if stopwatch.isRunning {
stopwatch.stop()
} else {
stopwatch.start()
}
}
}
}
}

FWIW, UserDefaults probably isn't the right place to store this startTime. I'd probably use a plist or CoreData or whatever. But I wanted to keep the above as simple as possible to illustrate the idea of persisting the startTime so that when the app fires up again, you can make it look like the timer was running in the background, even though it wasn’t.

Swift - Best way to run timer in background for iphone apps

In a word, no. You can ask for background time, but recent versions of iOS give you 3 minutes.

If you are a background sound playing app or navigation app you are allowed to run in the background for longer, but you have to ask for those permissions and the app review board will check.

The bottom line is that third parties can't really do a timer app that counts down an arbitrary time longer than 3 minutes.

You might want to use timed local notifications. You can make those play a sound when they go off. Search in the Xcode docs on UILocalNotification.

Keeping timers running when app enters background

As mentioned in Ewan Mellor's answer, you will not be able to rely on a timer while the app is in the background. So you will need to adjust as necessary when your app returns to the foreground.

Upon first reading the documentation, it might seem like viewWillAppear and viewWillDisappear (or viewDidDisappear) are the correct places to handle this. However, they do not get called when the app moves to/from the background.

Instead, you can make use of two notifications, UIApplicationWillResignActiveNotification and UIApplicationDidBecomeActiveNotification. The first notification will be sent to your app when it is about to go into the background. The second notification will be sent to your app when it is about to return to the foreground.

So in viewWillAppear you can register for the notifications as follows:

override func viewWillAppear(animated: Bool) {
// some other code
NSNotificationCenter.defaultCenter().addObserver(self, selector: "activeAgain", name: "UIApplicationDidBecomeActiveNotification", object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "goingAway", name: "UIApplicationWillResignActiveNotification", object: nil)

}

where activeAgain and goingAway are two functions you've written to enable and disable the timer. So based on the code snippet in your question, they would look something like this:

func activeAgain() {
let newTime = // calculate how much time is left (in seconds)
individualTaskTimer = NSTimer.scheduledTimerWithTimeInterval(newTime, target: self, selector: "deleteTopTask", userInfo: nil, repeats: true)

}

func goingAway() {
individualTaskTimer.invalidate()
}

Note that you need to unregister for the notifications when you switch away from this view. Doing this in viewWillDisappear is probably a good spot.

iOS Timer in the background

It is impossible to do something when the application is not in the foreground, with 100% certainty. You can use background fetch to be woken up periodically, but you can not control when it happens.

While there are some technical workarounds, and potentially even some hacky solutions (playing silent audio in the background), it sounds to me like your problem can be solved without the use of timers and background fetch.

Simply implement remote notifications. When your iOS receives a notification for your app, it will wake the app up and let it process for a while. You can then control what notifications are shown to the user, and maybe load some data in the background.

In broad terms, you need to:

  • register for remote notifications when your app starts (typically in application:didFinishLaunching:)
  • when you receive a notification token, send it to the web server that will send you the notifications (this token can change, so be sure to keep sending it to your web server whenever you start the app)
  • when new content is available, your web server can use the token to send a payload to your application (your choice of language probably already has a library for this. E.g. if you use Ruby, there is Houston)


Related Topics



Leave a reply



Submit