swiftswiftuibackground-taskbackground-fetchbgtaskscheduler

background task (.backgroundTask) doesn't work in SwiftUI


I've configured background task in plist.info with an identifier updateCountry and Background Modes fetch and processing as capability. I have locationManager with these functions:

func registerBackgroundTask() {
    BGTaskScheduler.shared.register(forTaskWithIdentifier: "updateCountry", using: nil) { task in
        self.handleAppRefresh(task: task as! BGAppRefreshTask)
        print("register backgoundtask")
    }
}

func handleAppRefresh(task: BGAppRefreshTask) {
    print("registrando pais")
    scheduleBackgroundTask()
    DispatchQueue.global().async {
        self.detectCountry()
        
        task.setTaskCompleted(success: true)
        print("Background task completed.")
    }
}

func scheduleBackgroundTask() {
    print("request updateCountry")
    let request = BGAppRefreshTaskRequest(identifier: "updateCountry")
    request.earliestBeginDate = Date(timeIntervalSinceNow: 60 * 1) // 1 minute
    
    do {
        try BGTaskScheduler.shared.submit(request)
        print("Background task scheduled successfully.")
    } catch {
        print("Unable to submit task: \(error)")
    }
}

I'm calling this functions in this way where handleAppRefresh calls a class function to detect the country where you are and add to an array... Everything seems fine, and I have no error, but nothing is printed in the console and doesn't work or update country list...

var body: some Scene {
    WindowGroup {
        ContentView()
        
    }
    .modelContainer(container)
    .backgroundTask(.appRefresh("updateCountry")) {
        await locationManager.registerBackgroundTask()
    }
}

Anyone knows how to fix it or where is the problem?


Solution

  • .backgroundTask(.appRefresh("updateCountry"))
    

    is the SwiftUI version of

     BGTaskScheduler.shared.register
    

    Change your code to something like

    .backgroundTask(.appRefresh("updateCountry")) {
        //The task you want performed.
        scheduleBackgroundTask() //Must be sync or async/await, don't use GCD
        detectCountry() //Must be sink or async/await, don't use GCD
    
    }
    

    And don't forget to call scheduleBackgroundTask() somewhere else for the first time.

    When in development mode you can trigger a background task on demand by placing a breakpoint after submit

    do {
        try BGTaskScheduler.shared.submit(request)
        print("Background task scheduled successfully.")
    
        //ADD breakpoint here
    } catch {
        print("Unable to submit task: \(error)")
    }
    

    Then when you run the app and the breakpoint gets triggered type in the debugger.

    e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"TASK_IDENTIFIER"]
    

    with TASK_IDENTIFIER being "updateCountry" in your case.

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

    But something to be aware... Background Tasks won't run every minute. You can suggest that but Apple will decide when to run, dont expect anything more often than every hour.

    For something like location you are better off using LocationManager.

    https://developer.apple.com/documentation/corelocation/handling_location_updates_in_the_background#

    And since you are monitoring "Country" "Significant Change In Location" might be the right solution.

    https://developer.apple.com/documentation/corelocation/cllocationmanager/1423531-startmonitoringsignificantlocati