Run Background Timer Swift

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!

Background timer running

Until iOS 12, you could not been able to run your app in background mode, but from iOS 13 (beta) you can.
Apple has added framework BackgroundTasks which can add ability to do some task in background mode.

https://developer.apple.com/documentation/backgroundtasks/bgtaskscheduler

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.



Related Topics



Leave a reply



Submit