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
Nsurl from String Gets Truncated, Dots on the Uivideoeditorcontroller's Video Path Swift 2 iOS 8
How to Have Either or Wherefields for Firestore Query
Setting Observer for Swift Objects/Properties
Swift Tuple Has Unexpected Print Result
Getting the Time Remaining in the Time Interval of a Timer in Swift
Filtering Dictionary in Swift 4 Fails in Xcode, But Succeeds in Playground
How to Read Ansi Escape Code Response Value in Swift
Can Openssl Be Bundled for Wget Wrapper App to Reference in Xcode Project
Why Does Classa Adopting Protocolb Not Satisfy the Protocolb Requirement
Nsurl Fail Able Initialiser Initwithstring: Does Not Return Nil on Empty String in Swift
Swift Why Isn't My Date Object That's (Equatable) Equal After Converting It to a String and Back
Change Mkmarkerannotationview Size
Node.Physicsbody.Joints Downcasting Error
Error: Argument Type Double/String etc. Does Not Conform to Expected Type "Anyobject"