I am trying to setup some BackgroundTasks
to run some code periodically (say once every day) while the app is closed. I believe the BackgroundTasks
API on swift is just for that? (Please let me know if I'm mistaken). I followed the article here on Apple's Docs to implement it, and adjusting it to fit SwiftUI.
Problem: The background task never fires but is "pending" (as seen in the images)
Disclaimer: I did add the Background Modes
capability and checked Background fetch
and also Background processing
, and added the identifer to the Info.plist
Code:
Main
- Setup app delegate, get scene, set UserDefaults counter, button to get all pending tasks, text showing the UserDefault counter, Button to add to UserDefault counter, call the schedule()
task when app enters background
import SwiftUI
import BackgroundTasks
@main
struct GyfterApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
@Environment(\.scenePhase) var scene
@AppStorage("test") var test = 1
var body: some Scene {
WindowGroup {
VStack {
Button("Test") {
BGTaskScheduler.shared.getPendingTaskRequests { all in
print("Pending Tasks Requests", all)
}
}
Text("\(test)")
Button("ADD") {
test = test + 1
}
}
.onChange(of: scene) { newValue in
switch newValue {
case .background:
print("Entered Background")
appDelegate.schedule()
default:
break
}
}
}
}
}
Operation
- Operation for the BackgroundTasks
, it prints a message and adds 1 to the UserDefaults counter
class OP: Operation {
@AppStorage("test") var test = 1
override func main() {
print("OPERATION RAN")
test = test + 1
}
}
AppDelegate
-
- Register task with given identifier, print message when it runs, and print the output of the register call to see if it was registered (it gets registered but doest not call
handleSchedule(task:)
) - Schedule the task request with identifier, for 60 seconds ahead, submit to the scheduler (
schedule()
only gets called when app enters background, not whenhandleSchedule(task:)
is supposed to fire which never gets called) - handleSchedule(task:) print statement, call
schedule()
, createOperationQueue
, createOperation
, setexpirationHandler
andsetTaskCompleted
, add operation to the queue
class AppDelegate: NSObject, UIApplicationDelegate {
@AppStorage("test") var test = 1
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
print("LAUNCHED")
let a = BGTaskScheduler.shared.register(forTaskWithIdentifier: "IDENTIFIER", using: nil) { task in
print("REGISTERED")
self.test = self.test + 1
self.handleSchedule(task: task as! BGAppRefreshTask)
}
print(a)
return true
}
func schedule() {
let request = BGAppRefreshTaskRequest(identifier: "IDENTIFIER")
request.earliestBeginDate = Date(timeIntervalSinceNow: 60)
do {
try BGTaskScheduler.shared.submit(request)
} catch {
print("Could not schedule app refresh: \(error)")
}
}
func handleSchedule(task: BGAppRefreshTask) {
print("HANDLING SCHEDULE")
schedule()
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
let operation = OP()
task.expirationHandler = {
print("BG Task Expired")
queue.cancelAllOperations()
}
operation.completionBlock = {
print("OPERATION COMPLETED")
task.setTaskCompleted(success: !operation.isCancelled)
}
queue.addOperation(operation)
}
}
Console log: Display of the print statements from the function calls
- Line 1: App launched
- Line 2:
BGTaskScheduler
"registered" (supposedly) the task with identifier - Line 3: Array of the pending tasks
- Line 4: App entered background (and
schedule()
got called) - Line 5: Array of the pending tasks
LAUNCHED
true
Pending Tasks Requests []
Entered Background
Pending Tasks Requests [<BGAppRefreshTaskRequest: INDENTIFIER, earliestBeginDate: 2022-02-28 02:57:50 +0000>]