I've searching and trying for too long how can I solve my problem.
I have orders and I need to check for their updates on my backend C# per hour. Even on background state or foreground.
I have tried:
var timer = Timer.scheduledTimer(withTimeInterval: 5.0, repeats: true) {
timer in
self.updatePedidos(timer: timer)
}
func updatePedidos() {
print("background started")
let strdata = Functions.getMostRecentDtPedido()
Functions.loadOrdersFromLastSyncByApi(strdata)
}
Also:
func applicationDidEnterBackground(_ application: UIApplication) {
Timer.scheduledTimer(timeInterval: 0.4, target: self, selector: #selector(self.updatePedidos), userInfo: nil, repeats: true)
}
#objc func updatePedidos() {
print("background started")
let strdata = Functions.getMostRecentDtPedido()
Functions.loadOrdersFromLastSyncByApi(strdata)
}
Also:
func applicationDidEnterBackground(_ application: UIApplication) {
UIApplication.shared.setMinimumBackgroundFetchInterval( 5 )
}
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler:
#escaping (UIBackgroundFetchResult) -> Void) {
print("background started")
let strdata = Functions.getMostRecentDtPedido()
Functions.loadOrdersFromLastSyncByApi(strdata)
if let newData = fetchUpdates() {
addDataToFeed(newData: newData)
completionHandler(.newData)
}
completionHandler(.noData)
}
And last that I cant put a timer:
NotificationCenter.default.addObserver(self, selector: #selector(updatePedidos), name:UIApplication.didEnterBackgroundNotification, object: nil)
#objc func updatePedidos() {
print("background started")
let strdata = Functions.getMostRecentDtPedido()
Functions.loadOrdersFromLastSyncByApi(strdata)
}
All of them doesn't print "background started" on background state, just on foreground. I added on info.plist:
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
</array>
You can try it using silent push notification. Every hour send 1 silent push notification and wake up the application in background mode and execute your task for some time.
You can read more detail for background task execution in below article:
https://www.hackingwithswift.com/example-code/system/how-to-run-code-when-your-app-is-terminated
Is there a way to wakeup suspended app in iOS without user or server intervention
Related
I'm trying to run a function every day at 8 am , I've tried background fetch with minimum interval 84600(23h 30 min) ,but it doesn't run on my iPhone . What should I do ?
Here is what I wrote in app delegate :
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
FirebaseApp.configure()
GADMobileAds.sharedInstance().start(completionHandler: nil)
UIApplication.shared.setMinimumBackgroundFetchInterval(84600)
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
func application(_ application: UIApplication, shouldSaveApplicationState coder: NSCoder) -> Bool {
return true
}
func application(_ application: UIApplication, shouldRestoreApplicationState coder: NSCoder) -> Bool {
return true
}
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
let sb = UIStoryboard(name: "Main", bundle: nil)
let activityVC = sb.instantiateViewController(withIdentifier: Constants.Storyboard.activityVC) as! ActivityViewController
activityVC.updateUI()
}
}
You can use a timer constructor like this:
let date = Date().addingTimeInterval(5)
let timer = Timer(fireAt: date, interval: 0, target: self, selector: #selector(runCode), userInfo: nil, repeats: false)
RunLoop.main.add(timer, forMode: .common)
This example launch func runCode after 5 seconds. If you passed repeats to true, and interval to 1, this launch "runCode" after 5 sec and repeats every 1 seconds after.
Now you can change date by your specified date.
source: https://www.hackingwithswift.com/example-code/system/how-to-run-code-at-a-specific-time
I solved the problem.
var backgroundUpdateTask: UIBackgroundTaskIdentifier = 0
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
return true
}
func applicationWillResignActive(application: UIApplication) {
self.backgroundUpdateTask = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({
self.endBackgroundUpdateTask()
})
}
func endBackgroundUpdateTask() {
UIApplication.sharedApplication().endBackgroundTask(self.backgroundUpdateTask)
self.backgroundUpdateTask = UIBackgroundTaskInvalid
}
func applicationWillEnterForeground(application: UIApplication) {
self.endBackgroundUpdateTask()
}
Timer doesn't work until it's activated. I want to do the timer update process even when the application is in the background. I update the current time with the tick function and when the timer is synchronized with the power on / off timer, I do the turn on or off. I want timer and the update process to work in the background.
override func viewDidLoad()
{
super.viewDidLoad()
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector:#selector(self.tick) , userInfo: nil, repeats: true)
}
#objc func tick() {
let formatter = DateFormatter()
formatter.dateFormat = "dd/MM/yyyy HH:mm"
labelTimer.text = formatter.string(from: Date())
zaman()
zaman1()
}
#objc func zaman(){
if timertext.text == String? (labelTimer.text!) {
zamanlayıcıfunc()
}else{
return
}
}
#objc func zamanlayıcıfunc()
{
if labelcheckbox.text == ("aç"){
updateState()
}
if labelcheckbox.text == ("kapat"){
updateState1()
}
}
#objc func zaman1(){
if timertext2.text == String? (labelTimer.text!) {
zamanlayıcıfunc1()
}else{
return
}
}
#objc func zamanlayıcıfunc1()
{
if labelcheckbox2.text == ("saatinde kapat"){
updateState1()
}
else{
updateState()
}
}
#objc func updateState(){
let ref = Database.database().reference()
ref.child("\(chip1InfoString1!)/states/\(self.ekle2.text!)").setValue(true)
getData()
}
Your application will not continue processing in the background. If all applications could do that the phone battery would easily and quickly be drained.
The OS will provide you with some limited background execution time, but if you use up too many resources it will be further limited. You can read more about it in Apple's documentation.
What you may need to do is keep track of when the app went into the background and foreground, using UIApplication.didEnterBackgroundNotification and UIApplication.willEnterForegroundNotification, to see how much time has passed.
Here is code which I execute in background when user close the app, but it is weird behavior , after endBackgroundUpdateTask() method is executed , DispatchQueue still doesn't stops...
I steel continuos get notification.
What am I doing wrong?
you can try to take this snipped of code and try for yourself, it is really weird
var backgroundUpdateTask: UIBackgroundTaskIdentifier!
func beginBackgroundUpdateTask() {
print("beginBackgroundUpdateTask")
self.backgroundUpdateTask = UIApplication.shared.beginBackgroundTask(expirationHandler: {
self.endBackgroundUpdateTask()
})
}
func endBackgroundUpdateTask() {
print("endBackgroundUpdateTask")
UIApplication.shared.endBackgroundTask(self.backgroundUpdateTask)
self.backgroundUpdateTask = UIBackgroundTaskInvalid
}
func doBackgroundTask() {
print("Strart")
DispatchQueue.global().async {
self.beginBackgroundUpdateTask()
// Do something with the result.
let timer = Timer.scheduledTimer(timeInterval: 10, target: self, selector: #selector(AppDelegate.displayAlert), userInfo: nil, repeats: true)
RunLoop.current.add(timer, forMode: RunLoopMode.defaultRunLoopMode)
RunLoop.current.run()
// End the background task.
self.endBackgroundUpdateTask()
}
print("Finish")
}
func displayAlert() {
print("displayAlert")
let note = UILocalNotification()
note.alertBody = "As a test I'm hoping this will run in the background every X number of seconds..."
note.soundName = UILocalNotificationDefaultSoundName
UIApplication.shared.scheduleLocalNotification(note)
}
func applicationDidEnterBackground(_ application: UIApplication) {
log.debug("applicationDidEnterBackground")
self.doBackgroundTask()
}
edit
var backgroundUpdateTask: UIBackgroundTaskIdentifier!
var timerr: Timer?
func beginBackgroundUpdateTask() {
appDeligate.log.debug("beginBackgroundUpdateTask")
self.backgroundUpdateTask = UIApplication.shared.beginBackgroundTask(expirationHandler: {
self.endBackgroundUpdateTask()
})
}
func endBackgroundUpdateTask() {
appDeligate.log.debug("endBackgroundUpdateTask")
UIApplication.shared.endBackgroundTask(self.backgroundUpdateTask)
self.backgroundUpdateTask = UIBackgroundTaskInvalid
timerr = nil
}
func doBackgroundTask() {
print("Strart")
DispatchQueue.global().async {
self.beginBackgroundUpdateTask()
// Do something with the result.
self.timerr = Timer.scheduledTimer(timeInterval: 10, target: self, selector: #selector(TestViewController.displayAlert), userInfo: nil, repeats: true)
RunLoop.current.add(self.timerr!, forMode: RunLoopMode.defaultRunLoopMode)
RunLoop.current.run()
// End the background task.
self.endBackgroundUpdateTask()
}
print("Finish")
}
func displayAlert() {
print("displayAlert")
let note = UILocalNotification()
note.alertBody = "As a test I'm hoping this will run in the background every X number of seconds..."
note.soundName = UILocalNotificationDefaultSoundName
UIApplication.shared.scheduleLocalNotification(note)
}
Really issue was with Timer, I was needed put this line
timerr?.invalidate()
here
func endBackgroundUpdateTask() {
appDeligate.log.debug("endBackgroundUpdateTask")
UIApplication.shared.endBackgroundTask(self.backgroundUpdateTask)
self.backgroundUpdateTask = UIBackgroundTaskInvalid
timerr?.invalidate()
}
But anyway it is little bit weird , because I thought if I was stopping backgroundUpdate() all tasks inside had to invalidate and purge automatically, but no.
Thanks #matt
I am trying to implement a basic func here which will be called when my app is backgrounded or suspended.
In reality, we aim to send about 5 a day so Apple should not throttle our utilisation.
I've put together the following which uses firebase and userNotifications, for now, it is in my app delegate.
import Firebase
import FirebaseMessaging
import UserNotifications
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var backgroundSessionCompletionHandler: (() -> Void)?
lazy var downloadsSession: Foundation.URLSession = {
let configuration = URLSessionConfiguration.background(withIdentifier: "bgSessionConfiguration")
configuration.timeoutIntervalForRequest = 30.0
let session = Foundation.URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
return session
}()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
FIRApp.configure()
if #available(iOS 10.0, *) {
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(
options: authOptions,
completionHandler: {_, _ in })
// For iOS 10 display notification (sent via APNS)
UNUserNotificationCenter.current().delegate = self
// For iOS 10 data message (sent via FCM)
FIRMessaging.messaging().remoteMessageDelegate = self
} else {
let settings: UIUserNotificationSettings =
UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
application.registerUserNotificationSettings(settings)
}
application.registerForRemoteNotifications()
let token = FIRInstanceID.instanceID().token()!
print("token is \(token) < ")
return true
}
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: #escaping () -> Void){
print("in handleEventsForBackgroundURLSession")
_ = self.downloadsSession
self.backgroundSessionCompletionHandler = completionHandler
}
//MARK: SyncFunc
func startDownload() {
NSLog("in startDownload func")
let todoEndpoint: String = "https://jsonplaceholder.typicode.com/todos/1"
guard let url = URL(string: todoEndpoint) else {
print("Error: cannot create URL")
return
}
// make the request
let task = downloadsSession.downloadTask(with: url)
task.resume()
NSLog(" ")
NSLog(" ")
}
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession){
DispatchQueue.main.async(execute: {
self.backgroundSessionCompletionHandler?()
self.backgroundSessionCompletionHandler = nil
})
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
NSLog("in didReceiveRemoteNotification")
NSLog("%#", userInfo)
startDownload()
DispatchQueue.main.async {
completionHandler(UIBackgroundFetchResult.newData)
}
}
}
#available(iOS 10, *)
extension AppDelegate : UNUserNotificationCenterDelegate {
// Receive displayed notifications for iOS 10 devices.
/*
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
let userInfo = notification.request.content.userInfo
// Print message ID.
//print("Message ID: \(userInfo["gcm.message_id"]!)")
// Print full message.
print("%#", userInfo)
startDownload()
DispatchQueue.main.async {
completionHandler(UNNotificationPresentationOptions.alert)
}
}
*/
}
extension AppDelegate : FIRMessagingDelegate {
// Receive data message on iOS 10 devices.
func applicationReceivedRemoteMessage(_ remoteMessage: FIRMessagingRemoteMessage) {
print("%#", remoteMessage.appData)
}
}
extension AppDelegate: URLSessionDownloadDelegate {
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL){
NSLog("finished downloading")
}
}
The results are as follows:
When the app is in the foreground:
I get the log "in startDownload func"
I get the log "finished downloading".
When the app is in the background:
I get the log "in startDownload func"
I do not get the log "finished downloading".
The silencer isn't working i.e. when the app is backgrounded, I am still getting the notification in the tray.
I am using Postman to send the request and tried the following payload, which results in the console error 'FIRMessaging receiving notification in invalid state 2':
{
"to" : "Server_Key",
"content_available" : true,
"notification": {
"body": "Firebase Cloud Message29- BG CA1"
}
}
I have the capabilities set for background fetch and remote notifications. The app is written in swift 3 and uses the latest Firebase
EDIT: Updated AppDelegate to include funcs as per comment
A few observations:
When your app is restarted by handleEventsForBackgroundURLSessionIdentifier, you have to not only save the completion handler, but you actually have to start the session, too. You appear to be doing the former, but not the latter.
Also, you have to implement urlSessionDidFinishEvents(forBackgroundURLSession:) and call (and discard your reference to) that saved completion handler.
You appear to be doing a data task. But if you want background operation, it has to be download or upload task. [You have edited question to make it a download task.]
In userNotificationCenter(_:willPresent:completionHandler:), you don't ever call the completion handler that was passed to this method. So, when the 30 seconds (or whatever it is) expires, because you haven't called it, your app will be summarily terminated and all background requests will be canceled.
So, willPresent should call its completion handler as soon it done starting the requests. Don't confuse this completion handler (that you're done handling the notification) with the separate completion handler that is provided later to urlSessionDidFinishEvents (that you're done handling the the background URLSession events).
Your saved background session completion handler is not right. I'd suggest:
var backgroundSessionCompletionHandler: (() -> Void)?
When you save it, it is:
backgroundSessionCompletionHandler = completionHandler // note, no ()
And when you call it in urlSessionDidFinishEvents, it is:
DispatchQueue.main.async {
self.backgroundSessionCompletionHandler?()
self.backgroundSessionCompletionHandler = nil
}
I have the following code in my AppDelegate for when my application enters the background:
var backgroundUpdateTask: UIBackgroundTaskIdentifier!
func beginBackgroundUpdateTask() {
self.backgroundUpdateTask = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({
self.endBackgroundUpdateTask()
})
}
func endBackgroundUpdateTask() {
UIApplication.sharedApplication().endBackgroundTask(self.backgroundUpdateTask)
self.backgroundUpdateTask = UIBackgroundTaskInvalid
}
func doBackgroundTask() {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
self.beginBackgroundUpdateTask()
// Do something with the result.
var timer = NSTimer.scheduledTimerWithTimeInterval(5, target: self, selector: "displayAlert", userInfo: nil, repeats: false)
NSRunLoop.currentRunLoop().addTimer(timer, forMode: NSDefaultRunLoopMode)
NSRunLoop.currentRunLoop().run()
// End the background task.
self.endBackgroundUpdateTask()
})
}
func displayAlert() {
let note = UILocalNotification()
note.alertBody = "As a test I'm hoping this will run in the background every X number of seconds..."
note.soundName = UILocalNotificationDefaultSoundName
UIApplication.sharedApplication().scheduleLocalNotification(note)
}
func applicationDidEnterBackground(application: UIApplication) {
self.doBackgroundTask()
}
I'm hoping that it executes a UILocalNotification() every X number of seconds specified in the NSTimer.scheduledTimerWithTimeInterval() however it only executes once.
I'm still trying to get my head around how background tasks work. Is there something I'm missing?
In the code sample, the timer you create will only fire once as you have set the "repeats" value to false in the initialiser.