I would like to clarify a few moments about traking user location while app is suspended. I have read a lot of articles about it but didn't find any clear answer.
Is it possible to create a local notification based on a user's location when application is suspended?
If it's possible, how my app's architecture has look like? Is my CLLocationManager subclass instance needs to be declared in AppDelegate file or it can be created as variable of some controller?
There are a couple of different ways to handle this.
You can set up a region-based local notification. That displays a message to the user if your app is not in the foreground. Your app only gets notified/launched if the user taps the action button on the local notification.
Another way to handle it:
You use the Core Location manager to create "geofence" regions that the system monitors on your app's behalf.
When your app is launched you should create an instance of the location manager and set up a delegate. You need to handle the process of asking the user for permission for location updates, and permission for always monitoring the user's location. That is a fussy, multi-step process, and if you miss a step it doesn't work. See the docs for more information. (I always have to back and re-read them when I'm setting up a new app with location services, and usually don't get it right the first time.)
When you've done that, the system will launch your app if it's not running when you receive a region enter/exit event. Once you create the location manager and set up a delegate, that delegate gets notified about the region enter/exit event.
In your handler for region enter/exit events you can post a local notification to yourself if you want to.
You can register the user for a local notifications using the region property.
//latitude & longitude come from your CLLocationManager delegates
let region = CLCircularRegion(center: CLLocationCoordinate2DMake(45.5017, 73.5673), radius: 1500, identifier: "identifier")
region.notifyOnExit = false
region.notifyOnEntry = true
let notification = UILocalNotification()
notification.region = region
notification.regionTriggersOnce = true //only show this notification once
notification.alertTitle = "Foo"
notification.alertBody = "Hello World"
UIApplication.shared.scheduleLocalNotification(notification)
Note that you can have at most 64 local notifications:
https://developer.apple.com/library/ios/documentation/iPhone/Reference/UILocalNotification_Class/
UPDATE: "You can only monitor a maximum of 20 location regions at one time. (and that's a combined total of 20 geofence regions and beacon regions.)" - DuncanC
Is it possible to create a local notification based on a user's location when application is suspended?
Yes. When you use background location monitoring, if your app is not active, it is woken in the background long enough to receive an event from the runtime. Thus, your app is now temporarily running. At that moment, creating a local notification is legal.
If it's possible, how my app's architecture has look like? Is my CLLocationManager subclass instance needs to be declared in AppDelegate file or it can be created as variable of some controller?
The event from the runtime is going to be sent to your location manager's delegate. Therefore your location manager needs to exist and it needs to have a delegate. It doesn't have to be a property of the app delegate, but it certainly needs to be a property of some instance that actually exists, so it if is a view controller, it had better not be a view controller that is not always present.
Note that if your app has been terminated while suspended (which is always a possibility), it will be launched from scratch (in the background) in order to receive this event. In that case, you can learn from the options: dictionary in didFinishLaunchingWithOptions: that this is because of an incoming location event, and thus you can respond by doing whatever is necessary in order to get yourself a location manager and a delegate.
Related
I am trying to fetch some arrival times for buses when a user approaches a stop, I have tested to ensure that the regions are correctly being trigged by sending a basic local notification and I have also tested my web service call to ensure it is working properly.
However I am having a hard time fetching the info then sending a notification.
Here is my code:
var bgTask: UIBackgroundTaskIdentifier = UIBackgroundTaskIdentifier()
bgTask = UIApplication.shared().beginBackgroundTask {
self.webService?.getStopEstimates(routeStopIds: stopRouteIdSet, routeNameDict: routeNameDict, completion: { (result) in
if result == "error" {
return
}
let notification = UILocalNotification()
notification.fireDate = Date(timeIntervalSinceNow: 1)
notification.alertTitle = region.identifier + " Stop Details"
notification.alertBody = result
notification.soundName = UILocalNotificationDefaultSoundName
UIApplication.shared().scheduleLocalNotification(notification)
})
UIApplication.shared().endBackgroundTask(bgTask)
}
Any one see why it might not be sending? I have enabled background fetch and location services. This is inside didEnterRegion
I did the same thing (when a user approach a store that has a discount he/she will be notified).
First of all I would I would suggest you to download all the data and save them on Core Data or SQLite if that is an option.
Second take a look at Significant Location Change here. It will update your location in the background each 500 meters if you are moving and it will save a lot of battery instead of the didUpdateLocation.
Third after using SLC, on each call, fetch from your Core Data or SQLite the 20 nearest locations of your current location and add them for monitoring(Use Haversine to calculate all the points distances from your current location and then get the 20 nearest). Each time SCL is called update the regions that you monitor(if saving your data offline is not an option I would recommend to send the request at your webservice here. The ideal scenario is that you will send your location to your webservice and it will reply back with the nearest points. then add them for monitoring)
When the didEnterRegion called make sure you have your data downloaded and then create a local notification.
Let me know if you need more details
PS. Have in mind that your app might be terminated (in background) by the system if is consuming a lot of resources. In that case you have to reinitialise everything you need for pulling and showing the data (Networks requests, Location manager etc.) You can detect that the App relaunched because of a location event in AppDelegate's didFinisingLaunchingWithOptions:
if ([launchOptions objectForKey:UIApplicationLaunchOptionsLocationKey]) {}
check this example on GitHub because I think your issue here is how you are handling the background task. If the web service call took too long, the background task will be invalidated. I don't know how well you know ObjC but in the example above, you will find a BackgroundTaskManager class handling that for you.
Things you should make sure of are:
Background Fetch in Target Settings (Background Modes section) are
ON.
You have asked and obtained "always" authorization (in plist)
Location Updates in Target Settings (Background Modes section) are ON.
Registered for User Notifications in appDidFinishLaunching in application delegate.
Renew expired background tasks if service took too long.
In the case of calling startUpdatingLocation while your app is in the background, CoreLocation still responds by starting the location updates for your app, but the system will no longer hold an assertion for you, and your app will be suspended once the allowed time in the background is spent. This time is (currently) is 10 seconds. But actually your app is now suspended and can no longer receive the updates. There is a way to extend this duration to (currently) up to 3 minutes by employing beginBackgroundTaskWithExpirationHandler: read more about it in Apple's CoreLocation Documentation
Whether this extended time is enough will depend on your app.
For your use case, Significant Location Change service is quite efficient.
You can start location updates when your app is in the foreground and defer location updates while your app is in the background. You can also read Apple's documentation about Significant Location Change
I am working on a project in which i am trying to get location updates in all states, even when app is terminated. I have used all possible solutions but still it's not working in case of termination.For now I want to clear a doubt - I am using startUpdatingLocation() for foreground and background. As we know that startMonitoringSignificantLocationChanges() is the only method that relaunch app in case of any location update. Can we call "startMonitoringSignificantLocationChanges()" in applicationWillTerminate() method? and Will that work and relaunch app when there is any significant location update? Please tell me.
Thank!!
You cannot do that in applicationWillTerminate(),because closure won't return a value right now.If you want to get user location all the time,try Background Mode.
This is the description in Apple Document:
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.
It clearly tells you how to get the location.
I need to check for device location every one hour or so and if the location is outside a particular area ( say the device has been taken out of the office premises ), do some action (like show a local notification saying "Hey! The device is outside the office").
To do this, I need to keep checking for location every one hour even though the app is killed. How to keep the app stay alive like forever though it has been terminated by the user.
Yes you can do it but if your system is deployment target is greater then 7.0.
In ios7.1 there is method called startMonitoringSignificantLocationChanges. This method updates location in background and even if application is terminated as per apple document:
Starts the generation of updates based on significant location changes.
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.
I found one demo for this may this help you. look this link http://mobileoop.com/getting-location-updates-for-ios-7-and-8-when-the-app-is-killedterminatedsuspended
Thanks for your answers. The best way do this is to use Geofencing.
It uses startMonitoringForRegion:(CLRegion *)region where we provide the latitude and longitude of the centre of the region and also the radius of the region.
When the device moves into this region, didEnterRegion gets called.
When the device moves out of this region, didExitRegion gets called.
And yes it works even when the app is terminated/backgrounded.
In addition, we have to set NSLocationAlwaysUsageDescription in the App's Info.plist file which you will find under the Supporting Files.
View this article for detailed information.
http://www.devfright.com/how-to-use-the-clregion-class-for-geofencing/
Inorder to test this in a simulator, I used local notifications when it enters and exits the region using UILocalNotification in the didEnterRegion and didExitRegion methods.
`UILocalNotification *notification = [[UILocalNotification alloc] init];
notification.fireDate = [[NSDate date] dateByAddingTimeInterval:1];
notification.alertBody = #"You have entered the region";
[[UIApplication sharedApplication] scheduleLocalNotification:notification];`
Now change the custom location of the iOS simulator (Debug -> Location -> Custom Location), and provide latitude and longitude within the region and you get a notification saying "You have entered the region" and change the custom location to latitude and longitude outside the region, you will get a notification saying "You have exited the region".
I've been working on a new app that leverages the use of location based reminders via geofencing.
As far as I understand, in order to do that, the app must request authorisation via:
CLLocationManager.requestAlwaysAuthorization()
So far, it works as expected, but I'm curious how for example the "Reminders" app, and others such as Omnifocus only request for "When in use" or:
locationManager.requestWhenInUseAuthorization()
Those apps are still able to provide location based reminders without requesting always authorisation. I also noticed that these apps won't trigger the "location" icon on the status bar while in background, which my app does. How do they get these apps to deliver those notifications then?
Thanks!
D
Found the answer while watching the WWDC CLLocation updates to iOS 8 available here:
https://developer.apple.com/videos/wwdc/2014/?id=706
Reminders and similar apps work without requiring for AlwaysAuthorization since they take advantage of the UILocalnotification framework changes. Since iOS 8, it supports Region Based Triggering.
So now if you do not need to actually "Launch" your app in the background and do something when location changes, and just present a notification, you can just use UILocalNotification and RegionBased Triggering.
#NSCopying var region: CLRegion!
in iOS8, UILocalnotification has the 'region' property, which correspondes to a CLRegion object.
From Apple docs:
Assigning a value to this (region) property causes the local notification to be delivered when the user crosses the region’s boundary. The region object itself defines whether the notification is triggered when the user enters or exits the region. The default value of this property is nil.
You may specify a value for this property or the fireDate property but not both. Attempting to schedule a local notification that contains both a region and fire date raises an exception.
Is it possible when your app is not running but the user is still able to receive a push notifcation when he's near a particular location. This will require checking of the user's current latitude and longitude (even app is not running).
If it's possible, can you give some guidelines on how can I achieve it?
There are two methods you can make use of to track user location even when the app is not running:
Significant-location change service.
Call the method startMonitoringSignificantLocationChanges in a CLLocationManager instance. This will help significantly in saving the power but it does not provide with high precision compared to the second method below.
You do not need to set the location key in UIBackgroundModes if you opt for this method.
Standard location service.
Call the method startUpdatingLocation in a CLLocationManager instance. This takes a lot of device power but it provides higher precision. Remember to set location key in UIBackgroundModes to make sure the tracking is still working even if the app is killed.
You can use either one of the methods above. I use the first method combined with Region Monitoring to trigger local notification.
You can take a look at the following Apple documentation for a more thorough explanations and examples:
http://developer.apple.com/library/ios/#documentation/userexperience/conceptual/LocationAwarenessPG/CoreLocation/CoreLocation.html
Region(CLRegion) Based Notifications Even When the App is not Running at all
Well, this answer is not a quick one but Apple has introduced new concept in UILocalNotification. It is not required to send a push notification, iOS will automatically show a local notification when user enter/exits geographical area CLRegion. From iOS 8 and later, we can schedule a local notification based on location not by setting fireDate property.
let localNotification = UILocalNotification()
localNotification.alertTitle = "Hi there"
localNotification.alertBody = "you are here"
let region = CLCircularRegion(center: CLLocationCoordinate2D(latitude: 4.254, longitude: 88.25), radius: CLLocationDistance(100), identifier: "")
region.notifyOnEntry = true
region.notifyOnExit = false
localNotification.region = region
localNotification.timeZone = NSTimeZone.localTimeZone()
localNotification.soundName = UILocalNotificationDefaultSoundName
UIApplication.sharedApplication().scheduleLocalNotification(localNotification)
Here are more details from Apple.
You need to do 2 things:
startMonitoringSignificantLocationChanges with the CLLocationManager. That's on the code side. You probably want to listen on the notification, and do whatever with the information that you're getting. But, if the app isn't running, you won't get this information unless you do two:
In Capabilities: enable BackgroundModes, and Modes: Location updates. That way, I believe you'll always get notifications, even if your app slips off the inactive list.
However, the user can still turn it off (Settings->General->Background App Refresh).
You can do it when app is in background.
firstly..call CLLocationManager deleagte whenever app goes to background.
Whenever device will get a new latitude and longitude.. This deleagte will get fired.
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations;
Now call a webservice and update your current location.
Then send a push notification.