iOS run async method on background task? - ios

Stack Overflow. Here is what I´m dealing with.
I´m developing an iOS 8 app that starts monitoring significant location changes when app is terminated using startMonitoringSignificantLocationChanges(). Until here everything works fine. The app relaunches when it get a location change with the application:didFinishLaunchingWithOptions:. I configure a CLLocationManager object and get the CLLocation like it says in Apple reference:
If you start this service and your app is subsequently terminated, the
system automatically relaunches the app into the background if a new
event arrives. In such a case, the options dictionary passed to the
application:willFinishLaunchingWithOptions: and
application:didFinishLaunchingWithOptions: methods of your app
delegate contains the key UIApplicationLaunchOptionsLocationKey to
indicate that your app was launched because of a location event. Upon
relaunch, you must still configure a location manager object and call
this method to continue receiving location events. When you restart
location services, the current event is delivered to your delegate
immediately. In addition, the location property of your location
manager object is populated with the most recent location object even
before you start location services.
Like I said, this works pretty well. My problem is when I try to fetch data from a web service (like Twitter API with TwitterKit) with a asynchronous method; I just not get onto the completion handler. I was researching a lot about this subject and I read that apparently the method I use is running in its own thread.
Here is how I listen to the location change launch and create the background task:
var locationManager: CLLocationManager!
var bgTaskId: UIBackgroundTaskIdentifier!
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// API keys configuration
if let options = launchOptions {
if options[UIApplicationLaunchOptionsLocationKey] != nil {
self.bgTaskId = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({ () -> Void in
UIApplication.sharedApplication().endBackgroundTask(self.bgTaskId)
self.bgTaskId = UIBackgroundTaskInvalid
})
// Location manager configuration
// Async func
theAsyncRequest(){ (response) -> Void in
//
// NEVER GETS HERE
//
UIApplication.sharedApplication().endBackgroundTask(self.bgTaskId)
self.bgTaskId = UIBackgroundTaskInvalid
}
}
return true
}
// Some code when app launches the normal way
return true
}
I tried the same with a synchronous method like in Parse Framework eg. let objects = someQuery.findObjects() and it worked. But it doesn´t with an asynchronous method.
What do I´m missing in here? Is it possible what I´m trying to accomplish? I know this would be an easy task if I´d use a synchronous method.
Thanks in advance.

Related

How to track the time a user listens an specific podcast

I have an iOS app that is about podcasts and I want to track how long a user listens every podcast. I have tried the basic - when a user plays I save the timestamp and when stops it sends an event with the timestamp difference but it obviously doens't work because there's many edge cases.
I have issues to know when a user has the app in background and stops listening at some point through the the system controls. Also when the user or the system kills the app without tapping on "pause" or "stop". I think these 2 cases are my main non-tracked cases so far.
Any idea how can I build a working solution? I don't want/can't pay an external service - I am merely relying on Firebase.
Thanks!
You can override applicationWillTerminate method in your app, and save a current user progress to UserDefaults.
As docs say, you have few seconds to do it:
This method lets your app know that it is about to be terminated and
purged from memory entirely. You should use this method to perform any
final clean-up tasks for your app, such as freeing shared resources,
saving user data, and invalidating timers. Your implementation of this
method has approximately five seconds to perform any tasks and return.
Your code can look like this:
var player: AVPlayer!
func applicationWillTerminate(_ application: UIApplication) {
UserDefaults.standard.setValue(player.currentTime().seconds, forKey: "curPlayerTime")
}
Then, on application launch, you can restore it:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if let lastPlayerTime = UserDefaults.standard.value(forKey: "curPlayerTime") as? Double {
// update your player
}
return true
}

How to make intermitent HTTP POST requests in the background of an iOS app - Swift

Hi I'm making an app that sends POST location update requests to a web application. I have one function called locationUpdate that I'd like to call intermittently once the app is in the background however I have't found a way to do it yet.
I have all the basics like Allow Location Always set. The app currently makes one HTTP POST request right when it enters the background because of this code in the AppDelegate file:
func applicationDidEnterBackground(_ application: UIApplication) {
sendLocation.determineCurrentLocation()
}
I also have this in the AppDelegate because I've seen it in a few stack overflow answers:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum)
return true
}
So all this but the app doesn't call sendLocation.determineCurrentLocation() (The function that makes the POST request) more than once and that is when the app is closed.
Can anyone tell me how to implement intermittent calling of the sendLocation function?
EDITED to show my locationManager:
func determineCurrentLocation(){
locationManager.delegate = self
locationManager.allowsBackgroundLocationUpdates=true
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
if CLLocationManager.locationServicesEnabled(){
DispatchQueue.main.async {
self.locationManager.requestLocation()
}
}
else if (!CLLocationManager.locationServicesEnabled())
{
print("You need to enable location services to use this app")
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]){
locationManager.stopUpdatingLocation()
userLocation = locations[0] as CLLocation
locationStruct.latitude=userLocation?.coordinate.latitude
locationStruct.longitude=userLocation?.coordinate.longitude
print(userLocation?.coordinate.latitude)
print(userLocation?.coordinate.longitude)
sendLocationPost()
}
You won't be able to do what you are trying to do unless you set up your app as a turn-by-turn navigation app and ask for permission to get location updates in the background.
Apps only get a very short time in the background before they are suspended.
You can ask for more background time with the beginBackgroundTask(expirationHandler:) call, but that's normally limited to 3 minutes of background time at max. (Search on that method to find the docs about it.)
Only certain types of apps are allowed to run indefinitely in the background. (Turn-by-turn navigation apps and streaming music apps.) You have to set up your info.plist to declare that your app is one of those types of apps. That is documented in the iOS docs that come with Xcode.
EDIT:
As Paul points out in his comment, you CAN ask for "always" location update permission and then ask for significant location change updates, although those are rather crude and you can't control the time interval between location updates (and won't get any updates until the OS decides the user has moved a significant distance.)

iOS: Why is fetch background function never fully executed?

My code in appdelegate for background fetch is never fully run. I have the background fetch option turned on and the plist updated.
I trigger the code by pressing Debug > Simulate Background Fetch
This is the code
func application(application: UIApplication, performFetchWithCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
User.getNotifications(User.getUserDetails()["id"].string!, callback: {(notifications) in
//update notification badge count
notificationBadgeCount = X
})
}
'User.getNotifications' looks like this
getNotifications(id: String, callback...){
alamofire.request(.GET....){ jsonResponse in
//GETS HERE
callback(jsonResponse)
}
}
When triggering the simulated background fetch, the alamofire GET request is sent and data is returned (I've checked the server and the call is sent), however, the app seems to suspend at (//GETS HERE) in the getNotifications call, so the rest the code in the background fetch (//update notification badge count) is never run.
The code seems to time out. I'm supposed to get 30s however it seems to time out in 5s or something.
Any idea why that section of code isn't executed?
NOTE: If I re-open the app manually, then the rest of the code executes.
performFetch has an incoming function called completionHandler. You must call that function to complete the fetch and stop the countdown clock. You are not doing that and you thus are timing out and the app is suspended.

health kit observer query always called when the app becomes active

The resultHandler of HKObserverQuery is always called when the app becomes active (background -> foreground)
But, I wrote the code of the query in didFinishLaunchingWithOptions method in AppDelegate.swift. I know the method is called when the app is launched not the app become active.
func application(application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
healthStore.authorizeHealthKit {
...
}
}
// other AppDelegate methods are empty
How do I make the handler of the query called only when my app is launched?
Why do you want to prevent the updateHandler from firing?
You can't control when the updateHandler of an HKObserverQuery fires while the query is running. You can prevent it from being called at all by stopping the query. It is designed to be called whenever there might be new HealthKit data matching your predicate. You should design your updateHandler such that it doesn't matter when it is called.
If you really wanted the observer query to not fire when your app returns to the foreground, you would need to stop the query completely with -[HKHealthStore stopQuery:] when your app enters the background, before it suspends.

How to get location in background even after phone restart in swift?

I'm developing an application which is tracking the location of the phone on time interval chosen by the user. I made it with timer which starts startUpdatingLocation, when it get the location stops updating sends the location to the server and starts timer again. Everything is done in background.
I need to get location data in background mode (in iOS 8) even after phone restarts and send the data to a server. I tried hundreds methods and no one works for me. So ... what i've got at this moment:
In info.plist:
Required background modes - location and fetch
NSLocationAlwaysUsageDescription - Location is required for this application to work properly
In AppDelegate:
var locationController: LocationController = LocationController() as LocationController;
func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool {
UIApplication.sharedApplication().setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum);
var bgTask = UIBackgroundTaskIdentifier()
bgTask = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler { () -> Void in
UIApplication.sharedApplication().endBackgroundTask(bgTask)
}
if(self.locationController.profileForTracking != nil && self.locationController.profileForTracking != "Disabled" && self.locationController.intervalForTracking != nil && self.locationController.trackingAllowed == true){
self.locationController.initLocationManager();
if(self.locationController.timer != nil){
self.locationController.timer = self.locationController.timer;
} else {
self.locationController.startTimerForLocationUpdate();
}
println("Location can now start ....");
}
return true
}
func application(application: UIApplication!, performFetchWithCompletionHandler completionHandler: ((UIBackgroundFetchResult) -> Void)!) {
var bgTask = UIBackgroundTaskIdentifier()
bgTask = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler { () -> Void in
UIApplication.sharedApplication().endBackgroundTask(bgTask)
}
if(self.locationController.profileForTracking != nil && self.locationController.profileForTracking != "Disabled" && self.locationController.intervalForTracking != nil && self.locationController.trackingAllowed == true){
self.locationController.initLocationManager();
if(self.locationController.timer != nil){
self.locationController.timer = self.locationController.timer;
} else {
self.locationController.startTimerForLocationUpdate();
}
println("Location can now start ....");
}
}
Everything seems to work (when app is in foreground or background/inactive) except when the phone was restarted or user terminate application. I think I'm doing something wrong and iOS does not wake up the application. How can I do it, and if I can do it somehow is there a way so get the location in time period chosen by the user (30 minutes, 1 hour ...). Thanks in advance!
While moving to background or when app terminated call this method
startMonitoringSignificantLocationChanges();
So if you reboot your device or app got killed, whenever there is significant location change in your location, OS will launch your app in background.
As per apple document
If you start this service and your app is subsequently terminated, the
system automatically relaunches the app into the background if a new
event arrives. In such a case, the options dictionary passed to the
application:willFinishLaunchingWithOptions: and
application:didFinishLaunchingWithOptions: methods of your app
delegate contains the key UIApplicationLaunchOptionsLocationKey to
indicate that your app was launched because of a location event. Upon
relaunch, you must still configure a location manager object and call
this method to continue receiving location events. When you restart
location services, the current event is delivered to your delegate
immediately. In addition, the location property of your location
manager object is populated with the most recent location object even
before you start location services.
What you want is no longer possible. As of iOS7, an application will only be kept alive in the background to receive locations if it was launched into the foreground by the user, not by the system (such as via a significant location change event). This change was mentioned briefly in in one of the talks at WWDC '13, probably the one titled "What's New in Core Location".
I had an app with this functionality, and it broke in iOS7. I spoke with Apple developer support, and they confirmed the change. The project I was working on was a origin-destination survey intended for an academic study; our solution ended up being to keep a set of local notifications, and regularly update their fire times into the future while we are running; if we ever stop running the user gets a notification to relaunch the app.
Be aware, as well, that if you ever tell core location to stop updating your location, you will not be able to turn that back on until you are again launched into the foreground by the user. The solution we used here is a combination of deferred location updates, and of turning the 'desired accuracy' way up, so that the phone isn't powering on the GPS or wifi antennas when it isn't necessary. This isn't ideal, obviously, but our battery performance has managed to be more reasonable then I had expected.

Resources