App is configured to receive location updates while in the background so as to keep the app active and the updates are being received successfully when app is in the background.
Darwin notifications have also been configured and are received only when the app is the current app in the foreground. As soon as the app is put in the background it stops receiving the Darwin Notifications.
Any thoughts on how to receive the Darwin Notifications while the app is in the background?
Code snippets below.
Building App in Swift2
in appdeligate
let callback: #convention(c)
(CFNotificationCenter!, UnsafeMutablePointer<Void>, CFString!, UnsafePointer<Void>, CFDictionary!) -> Void = {
(center, observer, name, object, userInfo) in
//Execute callback code
}
let exCb: CFNotificationCallback = unsafeBitCast(callback, CFNotificationCallback.self)
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(),nil ,exCb,"com.apple.springboard.hasBlankedScreen" as CFString,nil ,CFNotificationSuspensionBehavior.DeliverImmediately)
in viewcontroller
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
//locationManager.requestWhenInUseAuthorization()
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
Make sure you've activated the Location updates capability in Background Modes.
This will allow your app to receive location changes in the background and proceed with your logic.
A great library for this kind of functionality is INTULocationManager.
Related
I'm trying to send the user a local notification when a region is entered (at an immediate distance) while the app is closed. I have it currently working if the app is in the background, but can't get it to work if the app is closed. I've read other posts that say this is possible, but none of the solutions work and they are outdated. I'd appreciate some help in Swift 3.
Here's my code (all in AppDelegate):
In didFinishLaunchingWithOptions:
locationManager.delegate = self
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.allowsBackgroundLocationUpdates = true
locationManager.requestAlwaysAuthorization()
let uuid = UUID(uuidString: "someuuid")!
let beaconRegion = CLBeaconRegion(proximityUUID: uuid, identifier: "SomeBeacon")
beaconRegion.notifyEntryStateOnDisplay = true
beaconRegion.notifyOnEntry = true
beaconRegion.notifyOnExit = true
locationManager.startMonitoring(for: beaconRegion)
locationManager.startRangingBeacons(in: beaconRegion)
I also have didRangeBeacons implemented.
The code looks correct to allow detection when the app is closed. You do not need:
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.allowsBackgroundLocationUpdates = true
But they should not hurt anything.
You only mention trouble with the app closed, so I assume foreground detection works fine. Does it? If not, troubleshoot this first.
It is often difficult to properly test the app closed use case leading to failures due to test setup issues. A few tips may help:
iOS will only send a region entry event if it thinks it is out of region. Often in testing it thinks it is in region so you do not get an event. To ensure you are out of region, turn off your beacon or go out of range with the app in the foreground and wait until you get an exit callback. Only then should you kill the app to test closed detection.
If rebooting your phone, always wait 5 minutes after startup to be sure CoreLocation is fully initialized. And make sure you have followed rule 1.
Make sure you do not have a bunch of other beacon apps installed on the phone taking up all the Bluetooth detection hardware acceleration slots. If you do, detections in the background can be delayed up to 15 min. Uninstall all beacon apps then uninstall and reinstall yours.
If you follow the testing tips above you should see detection callbacks within a couple of seconds of the beacon being in the vicinity.
UPDATE As #davidgyoung pointed out to me on a different question, this method of using the UNLocationNotificationTrigger won't report the major and minor identifiers to you because it is using the monitoring API and not ranging which is what you need to obtain the major and minor numbers. This limits what all you might do with this wrapper API. It sure is convenient, but not as helpful as one might hope.
The new (iOS 10, Swift 3) way from what I can tell is to use the UserNotifications library and implement it with a UNNotificationRequest. You can pass it a beacon region via UNLocationNotificationTrigger. Now I know it seems counter intuitive for what you're wanting (notification when app is closed), however, you want to add the NSLocationWhenInUseUsageDescription key to your Info.plist instead of NSLocationAlwaysUsageDescription. I'm not sure why it is that way, but it's a necessary step. Here's the code I'm using for creating a (iBeacon) location based notification:
// Ensure you use a class variable in order to ensure the location
// manager is retained, otherwise the alert message asking you to
// authorize it will disappear before you can tap "allow" which
// will keep it from working
let locationManager = CLLocationManager()
// ...
// Somewhere in your class (e.g. AppDelegate or a ViewController)
self.locationManager.requestWhenInUseAuthorization()
let region = CLBeaconRegion(proximityUUID: UUID(uuidString: "YOUR-UNIQUE-UUID-STRING")!, identifier: "com.yourcompany.youridentifier")
region.notifyOnEntry = true
region.notifyOnExit = false
let content = UNMutableNotificationContent()
content.title = "Notification Title"
content.body = "Body text goes here"
// Not sure if repeats needs to be set to true here, but that's
// how I've implemented it
let trigger = UNLocationNotificationTrigger(region: region, repeats: true)
// Use the same identifier each time to ensure it gets overwritten
// in the notification system
let identifier = "BeaconLocationIdentifier"
let request = UNNotificationRequest.init(identifier: identifier, content: content, trigger: trigger)
// This is probably unnecessary since we're using the same identifier
// each time this code is called
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
// Make sure you set the notification center's delegate. My
// containing class is the AppDelegate. I implement the delegate
// methods there.
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter.current().add(request, withCompletionHandler: { (error) in
})
// Not sure if this is required either, but after I added this, everything
// started working
self.locationManager.startRangingBeacons(in: region)
Make sure you implement the notification center delegate methods:
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
// Called when the notification has been swiped or tapped by the user
// Do something with the response here
// Make sure you call the completion handler
completionHandler()
}
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
// Called when the app is in the foreground
// Do something with the notification here
// Make sure you call the completion handler
completionHandler([.alert, .sound])
}
Finally, you're going to need to authorize notifications first to get this all to work. You can use this code to do that:
let options: UNAuthorizationOptions = [.alert,.sound]
UNUserNotificationCenter.current().requestAuthorization(options: options) {
(granted, error) in
if !granted {
debugPrint("Something went wrong")
} else {
debugPrint("Notifications granted")
}
}
One last tip: I have an old metal tin that (sort of) acts as a Faraday cage for the beacon I'm using. I just close my beacon up in there for a minute or so and then the app detects that I've gone out of range. This is more convenient than trying to walk far enough away from the beacon or removing the battery (which doesn't seem to work at all for me). Some beacons have stronger signals than others and may still get through the tin, so YMMV.
My app requires to update location continuously, even if app is killed or removed from background. It works fine in foreground and even in background mode.but not working when app is killed.
I've tried some code.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
self.startLocationService()
if ((launchOptions?[UIApplicationLaunchOptionsLocationKey]) != nil) {
self.locationmanager = CLLocationManager()
self.startLocationService()
}
print("Location Updates started")
return true
}
func startLocationService()
{
self.locationmanager.requestWhenInUseAuthorization()
self.locationmanager.desiredAccuracy = kCLLocationAccuracyBest
self.locationmanager.allowsBackgroundLocationUpdates = true
self.locationmanager.requestAlwaysAuthorization()
self.locationmanager.delegate = self
if UIApplication.sharedApplication().backgroundRefreshStatus == .Available {
print("Background updates are available for the app.")
}
else if UIApplication.sharedApplication().backgroundRefreshStatus == .Denied {
print("The user explicitly disabled background behavior for this app or for the whole system.")
}
else if UIApplication.sharedApplication().backgroundRefreshStatus == .Restricted {
print("Background updates are unavailable and the user cannot enable them again. For example, this status can occur when parental controls are in effect for the current user.")
}
else
{
print("nothing")
}
self.locationmanager.startUpdatingLocation()
}
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.
// locationManager = CLLocationManager()
}
func applicationDidBecomeActive(application: UIApplication) {
self.startLocationService()
}
func applicationWillTerminate(application: UIApplication) {
self.locationmanager.startMonitoringSignificantLocationChanges()
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("did update locateion called-->..")
let location:CLLocation = locations[locations.count-1]
print(location.coordinate.longitude)
print(location.coordinate.latitude)
let newTime : NSDate = NSDate()
let calendar: NSCalendar = NSCalendar.currentCalendar()
let flags = NSCalendarUnit.Second
let components = calendar.components(flags, fromDate: oldTime, toDate: newTime, options: [])
//Update locations to server
self.call_service_insertLocation("location", loc: location)
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
print(error)
}
I hope this might help you.
Your app can be awaken when it is terminated by some reason (Memory pressure).
But there's limitation on background location update when app is terminated.
This is note from Apple's Location and Map programming guide.
If your app is terminated either by a user or by the system, the system doesn't automatically restart your app when new location updates arrive. A user must explicitly relaunch your app before the delivery of location updates resumes. The only way to have your app relaunched automatically is to use region monitoring or significant-change location service.
However, when a user disables the Background App Refresh setting either globally or specifically for your app, the system doesn't relaunch your app for any location events, including significant change or region monitoring events. Further, while Background App Refresh is off your app won't receive significant change or region monitoring events even when it's in the foreground.
So to receive location update in background, you should turn on Background App Refresh setting for your app (You can see the setting in Settings/Your App/Background App Refresh in your iPhone.)
Also only significant changes will be delivered when app is terminated and like location changes you mentioned here (I mean kCLLocationAccuracyBest) probably will not wake your app.
Further more, you should restart the location manager in your app delegate
s application:didFinishLaunching:withOptions method to retrieve next significant location changes. Also make sure return as soon as possible when location is retrieved in background mode or use background task mechanism to make app working more a few minutes in background. (2~3 mins).
Its possible, but you'll have to jump through a few hoops.
The only way to send location updates when killed is by using Region Monitoring (https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/LocationAwarenessPG/RegionMonitoring/RegionMonitoring.html).
When setup up, your app would be opened by the OS then you have a few seconds to process information before the app is killed. Its enough time to send location updates to your server/local storage.
There is also another API called CLVisit, however, its completely controlled by the operating system and not reliable.
I'd added CLLocationManager in my app using Swift in the AppDelegate file.
In the Appdelegate.swift file,
import CoreLocation
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, CLLocationManagerDelegate {
var locationManager: CLLocationManager!
In the didbecomeActive method:
func applicationDidBecomeActive(application: UIApplication) {
if((locationManager) != nil)
{
locationManager.stopMonitoringSignificantLocationChanges()
locationManager.delegate = nil
locationManager = nil
}
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.activityType = CLActivityType.OtherNavigation
locationManager.requestAlwaysAuthorization()
locationManager.startMonitoringSignificantLocationChanges()
}
If I use startUpdatingLocation, didUpdateLocations method gets called, but not for startMonitoringSignificantLocationChanges.
Why it's not getting called for startMonitoringSignificantLocationChanges.
I'm testing this in ios simulator. I don't know how to check in device.
It's working, but it's really hard to trigger significant location changes - it usually happens when the device is changing cell towers - I don't think it's possible to do with the simulator.
You'd probably have to get on a bike/car and travel AT LEAST a few kilometres.
There's a trick you can use though, that will trigger significant location change:
Switch Airplaine mode in your iPhone on and off with a few second intervals repeatedly, it should trick the device into thinking that it changed cell towers and trigger the significant location change.
In your simulator, goto Debug->Location->Custom and change location, then test it.
In your simulator, select Features -> Location -> Freeway Drive
Wait a bit for startMonitoringSignificantLocationChanges to trigger didUpdateLocations.
Note:
Apps can expect a notification as soon as the device moves 500 meters or more from its previous notification. It should not expect notifications more frequently than once every five minutes. If the device is able to retrieve data from the network, the location manager is much more likely to deliver notifications in a timely manner. startMonitoringSignificantLocationChanges()
I am trying to implement a location-baed navigation app on iOS. I will have a device in my pocket and want it notifies when criteria are met based on location information, such as Google Maps navigation notifies when you make a turn on street.
I assume I can make it with CoreLocation and local notification.
Here are what I have done, expect and actually got.
XCode Version 7.1 (7B91b) on OS X El Capitan 10.11.1
In Xcode, File -> New -> Project -> iOS Application -> Single View Application
Add CoreLocation framework to project
Enable "Location updates" background mode
Add NSLocationAlwaysUsageDescription key with value of "hello" to info.plist
Leave ViewController.swift untouched
Edit AppDelegate.swift as follows;
import UIKit
import CoreLocation
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, CLLocationManagerDelegate {
var window: UIWindow?
lazy var locationManager: CLLocationManager! = {
let manager = CLLocationManager()
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.pausesLocationUpdatesAutomatically = false
manager.delegate = self
manager.requestAlwaysAuthorization()
return manager
}()
var lastNotifictionDate = NSDate()
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let settings = UIUserNotificationSettings(forTypes: [.Alert], categories: nil)
UIApplication.sharedApplication().registerUserNotificationSettings(settings)
locationManager.startUpdatingLocation()
return true
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// Do something interesting.
// I make this function return immediately until 10sec passed after previous notification.
let now = NSDate()
if lastNotifictionDate.dateByAddingTimeInterval(10).compare(now) == .OrderedDescending {
//NSLog("Not yet")
return
}
//NSLog("It's time")
lastNotifictionDate = now
// Fire local notification, without sound configured for simplicity.
let notification = UILocalNotification()
notification.alertBody = "Hello"
UIApplication.sharedApplication().presentLocalNotificationNow(notification)
}
// ... other methods are untouched ...
}
The app works on simulator as I expect, namely;
Run the app in iPhone 5 Simulator and allow location and notification services
Press home button (Shift-Command-H).
In the simulator menu, Debug -> Location -> Free drive
The app displays a local notification alert every 10sec
The app acts different on my device, iPhone 5 with iOS 9.1;
Run the app via Xcode and allow location and notification services
Pressed home button
The device does not display notification alert either on home screen or notification center.
Then, I tap the app icon on the device to bring the app foreground, opened the notification center and found two notifications. I expect 5 notifications newly appear if I wait for 50 seconds, but no notification newly appear.
I understand Simulator behaves differently than the actual devices. For example, Simulator shows NSLog() and print() outputs on console during background, while the actual device does not.
I guess I did not configure something for devices or I am trying to do in unfeasible way, but I cannot find what it is. Could you please tell me what is wrong and how I can fix it?
Setting CLLocationManager's allowsBackgroundLocationUpdates to true solved my problem.
manager.allowBackgroundLocationupdates = true
If it is false (default), updates stops when suspended. Updates occur when foreground or background.
I am writing an iOS app that requires the device's GPS loction to be updated when a push notification is received.
I use a closure to get the current GPS location. This code runs perfectly when the app is in the foreground (Both "New remote notification" and "Got location" is printed to console), but when the app is in the background, only "New remote notification" (and no location error) is printed to console. As a result I don't have the GPS location of the device at this point, which is very important for my app's functionality.
Any help would be greatly appreciated.
Thanks.
I have in my Info.plist file for 'Required background modes';
App registers for location updates
App downloads content from the network
App downloads content in response to push notifications
My app also has access to location at all times, including the background (successfully tested at other points in the code): NSLocationAlwaysUsageDescription is in the Info.plist file
In my AppDelegate file:
func application(application: UIApplication!, didReceiveRemoteNotification userInfo:NSDictionary!) {
println("New remote notification")
var notification:NSDictionary = userInfo as NSDictionary
var loc: LocationManager?
loc = LocationManager()
loc!.fetchWithCompletion {location, error in
// fetch location or an error
if let myloc = location {
var lat = myloc.coordinate.latitude as Double
var lon = myloc.coordinate.longitude as Double
println("Got location")
} else if let err = error {
println(err.localizedDescription)
}
loc = nil
}
}
If app is not in foreground, make sure to ask for a little time to complete the request with beginBackgroundTaskWithExpirationHandler and then call endBackgroundTask when done.
For more information, see the Executing Finite Length Tasks in the Background Execution chapter of the App Programming Guide for iOS.