If a method is Deprecated, does this mean it will not work in updated systems?
I was using setMinimumBackgroundFetchInterval for background fetch and I believe it stopped working for ios 13 and newer versions.
Do you think it is because it is deprecated or a bug?
When I use Simulate Background Fetch it works fine, but it does not fetch when I really use the app.
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
let successCallback: (_ data: Double) -> Void = { (data) in
if data == nil {
// No new data
completionHandler(.noData)
} else {
// There is a new data set
self.bodyMassObserverQueryTriggered() // count todays steps and updates database
completionHandler(.newData)
}
}
getSteps(completion: successCallback) // count todays steps and updates database
}
and I have the following lines in applicationdidfinishwithoptions:
UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplication.backgroundFetchIntervalMinimum)
UIApplication.shared.setMinimumBackgroundFetchInterval(5*60)
Do I need anything else?
EDIT: One thing i found is i needed to remove BGTaskSchedulerPermittedIdentifiers from Info.plist according to apple docs:
Adding a BGTaskSchedulerPermittedIdentifiers key to the Info.plist disables application(:performFetchWithCompletionHandler:) and setMinimumBackgroundFetchInterval(:) in iOS 13 and later.
Related
I am new in background tasks. I have a small work that I am fetching tweets and If my app is in background mode then also it should fetch tweets, but I don't know how.
I am using simply Timer in Appdelegate didFinishLaunchOption Method. When I will close the app then it's not working. I am new in that so please any suggestion. Here below is my code:
Timer.scheduledTimer(timeInterval: 10, target: self, selector: #selector(getTweets), userInfo: nil, repeats: true).
func getTweets() {
let locationName = Helper.sharedInstance.userDefault.value(forKey: ModelKey.currentLocation) as? String
let accessToken = Helper.sharedInstance.userDefault.value(forKey: ModelKey.twitterAccessToken) as? String
if (locationName == "Bengaluru" && nil != accessToken) || (locationName == "Bangalore" && nil != accessToken){
tweetModel.getTweets(accessToken: accessToken!, city: ModelKey.blrcitytraffic, cityName: "Bengaluru")
}
}
Text to speech is also there but when I will close the app then it stops speaking. If I am not using app then also it can fetch tweets and text to speech should work using a background mode. How long does that work?
A background task means you need to use background threads. Threads in iOS are too many, but if you want to make only background task, you should use two threads; the main and background thread that their structure is:
DispatchQueue.global(qos: .background).async {
//background code
DispatchQueue.main.async {
//your main thread
}
}
So, you firstly initialize the global queue with background mode. This thread can be used for background task and then you must use main thread (only if you want) for doing something when the background task is finished. This can be an option. Another option should be applicationDidEnterBackground in appDelegate and you can only must put your code in that method.
You need to do three things:
In your Info.plist add the following entry for key Required background modes to allow background network access:
Required background modes: App downloads content from the network
In your AppDelegate add to your applicationDidEnterBackground():
func applicationDidEnterBackground(_ application: UIApplication) {
// Fetch no sooner than every (60) seconds which is thrillingly short actually.
// Defaults to Infinite if not set.
UIApplication.shared.setMinimumBackgroundFetchInterval( 60 ) )
}
Also in AppDelegate implement
func application(application: UIApplication, performFetchWithCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
var fetchResult: UIBackgroundFetchResult!
if doingYourStuffActuallyCreatesNetworkTraffic() {
fetchResult = UIBackgroundFetchResult.newData
} else if thereWasAnError() {
fetchResult = UIBackgroundFetchResult.failed
} else {
fetchResult = UIBackgroundFetchResult.noData
}
completionHandler( fetchResult )
return
}
There are still some pitfalls, e.g. there is no guaranteed maximum fetch interval, and background execution might behave substantially different in XCode/Simulator than on real devices.
You could take a look at this pretty similiar topic:
performFetchWithCompletionHandler never gets fired
and of course
https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html
I need to execute a task when the app is in background state. For example, when the app enters the background state, then every 5 minutes(app is in background in this time) a task is executed.
I tried with location changed but I can't use a precise location(for battery consume) then I used significant location changed but If user doesn't move or doesn't change cell tower location is not updated.
Can you help me about it?
Yo could use the iOS Background Fetch feature where you can specify minimum background fetch interval. But actual interval between successive invocation of your code will be determined by iOS framework.
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
let data: String? = nil
do {
//fetch some data
if let data = getSomeNewData() {
/* use the completionHandler to talk to the system and tell us if the app fetched new data, or if no data was available. */
completionHandler(.newData)
} else {
completionHandler(.noData)
}
} catch {
print(error)
completionHandler(.failed)
}
}
see also question: swift-ios-refreshing-app-data-when-in-background
Another option is to setup a server that will send a (silent) push notification to your app every 5 minutes that your app can react to.
I am trying to understand doing Quick Actions (3D Touch) for iOS 9.
I wanted the user to select 1 of 4 filter to be applied to image, so if I select item 1, I will set the NSUserDefaults.standardUserDefaults() to the filter, then show the correct picture with the applied filter.
In AppDelete.swift:
func application(application: UIApplication, performActionForShortcutItem shortcutItem: UIApplicationShortcutItem, completionHandler: (Bool) -> Void) {
var filterType:Int
switch (shortcutItem.type) {
...set filterType
}
NSUserDefaults.standardUserDefaults().setInteger(filterType, forKey:"filterType")
NSUserDefaults.standardUserDefaults().synchronize()
}
In ViewController.swift:
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector:"setDefaultFilter", name: UIApplicationWillEnterForegroundNotification, object:nil) // Handle enter from background
setDefaultFilter()
}
func setDefaultFilter() {
filterType = defaults.integerForKey("filterType")
...
imageView.image = filterImage(defaultImage!, filter:filters[filterType])
}
However, when enter the app from the menu, it will always show the last selection (not the current selection). If I select item 1, nothing happened. I select item 3, item 1 will appeared.
I have also try passing parameters via appDelegate and the result is the same. I believe there are some issues with life cycle.
Any ideas?
NSUserDefaults write data on flash, which may not be so fast.
You can wait a little longer, like observe UIApplicationDidBecomeActiveNotification other than UIApplicationWillEnterForegroundNotification.
Or you can use other ways to pass params, e.g., as an instance variable in AppDelegate.
didFinishLaunchingWithOptions method is always called before calling performActionForShortcutItem method to response to the quick action.
So, I think that you need to check what kind of quick action is selected in didFinishLaunchingWithOptions method. If the app is not launched from quick action, you just continue to your normal app launching process.(default filter)
And if you decide to handle quick action in didFinishLaunchingWithOptions, you have to return NO in didFinishLaunchingWithOptions.
You could get more idea from my demo project:
https://github.com/dakeshi/3D_Touch_HomeQuickAction
I'm sending an push notification a at 8am to start GPS in an application running in background. Push notification receiving, but GPS not started tracking. How can i achieve this? I m using following code.
func application(application: UIApplication,
didReceiveRemoteNotification userInfo: [NSObject : AnyObject],
fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
NSLog("userInfoComp %#",userInfo);
var app = UIApplication.sharedApplication()
var bgTask2 : UIBackgroundTaskIdentifier?
bgTask2 = app.beginBackgroundTaskWithExpirationHandler { () -> Void in
app.endBackgroundTask(bgTask2!)
bgTask2 = UIBackgroundTaskInvalid
}
if bgTask2 == UIBackgroundTaskInvalid {
return
}
PSLocationManager.sharedLocationManager().prepLocationUpdates()
PSLocationManager.sharedLocationManager().startLocationUpdates()
}
You can not start location service when your app is in background mode. You need to use startMonitoringSignificantLocationChanges when your app is active with user's permission (to track him even if user is not using the app) to get location update in background mode.
You will get location update even when app is terminated by user at every 5 min if user is not stationary.
If your app is in background you may get location update using startLocationUpdates but it's not reliable even if 3-4 apps are in background os may stop updating location.
I am trying to perform some actions triggered by changes to Apple Health Kit, triggered in the background of my Swift iOS app.
Here's my AppDelegate:
var healthManager : HealthManager?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
healthManager = HealthManager.sharedInstance
return true
}
And in the initialization of the HealthManager class I authorize use of Health Kit and call:
var sampleType = HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount)
var predicate = HKQuery.predicateForSamplesWithStartDate(startDate, endDate: endDate, options: HKQueryOptions.StrictStartDate)
var query = HKObserverQuery(sampleType: sampleType, predicate: predicate, updateHandler: stepsChangedHandler)
healthKitStore.executeQuery(query)
healthKitStore.enableBackgroundDeliveryForType(sampleType, frequency: .Immediate, withCompletion: {(succeeded, error) in
if succeeded {
println("Enabled background delivery of step changes")
} else {
if let theError = error {
print("Failed to enable background delivery of step changed. ")
println("Error = \(theError)")
}
}
})
This works beautifully when the app is open- the stepsChangedHandler is called when there's a health kit update, but when the app is out of focus it never is called. I've searched around and found a few ideas, but none of the fixes have seemed to work for me.
Thanks!
What you have should work, but my experience with the simulator up through iOS 8.4 and Xcode 6.4 is that the background updates are not triggered. However, in my testing this does work on a device. To try for yourself, hook up and run your app on a device then switch to Health.app and add a relevant data point.
If your query is set for immediate updates, you should see your log message in the console. Make sure stepsChangedHandler includes completionHandler().
According to the docs, the query runs on a separate background thread so your appdelegate code will only be called on initial launch.
In the documentation for the HKHealthStore Class, under enableBackgroundDeliveryForType:... there is a paragraph:
Some data types, such as step counts, have a minimum frequency of HKUpdateFrequencyHourly. This frequency is enforced transparently.
which explains why you won't see background updates as frequently as you are specifying. I'm not sure if theres a listing of which data types are included in the "some" quantifier.