We are working on an app which needs location subsequently on terminated state. I am trying to achieve this by enabling Background Modes services i.e location update and background fetch, but app doesn't relaunch silently after a significant change in location. I have Enabled properties like startMonitoringSignificantLocationChanges and allowsBackgroundLocationUpdates with my location manager on didFinishLaunchingWithOptions and on didUpdateLocations create CLRegion and enable locationManager startMonitoring(for: region) but all this doesn't works.
This is what I have written in my didFinishLaunchingWithOptions
if application.applicationState == .active {
LocationManager.shared.startMonitoringLocation()
}
// When there is a significant changes of the location,
// The key UIApplicationLaunchOptionsLocationKey will be returned from didFinishLaunchingWithOptions
// When the app is receiving the key, it must reinitiate the locationManager and get
// the latest location updates
// This UIApplicationLaunchOptionsLocationKey key enables the location update even when
// the app has been killed/terminated (Not in th background) by iOS or the user.
if launchOptions?[UIApplication.LaunchOptionsKey.location] != nil {
//You have a location when app is in killed/ not running state
LocationManager.shared.afterResume = true
LocationManager.shared.startMonitoringLocation()
LocationManager.shared.addApplicationStatus(toPList: "APP relaunch silently")
}
Location Manager
func startMonitoringLocation() {
if (anotherLocationManager != nil) {
anotherLocationManager!.stopMonitoringSignificantLocationChanges()
}
anotherLocationManager = CLLocationManager()
anotherLocationManager!.allowsBackgroundLocationUpdates = true
anotherLocationManager!.pausesLocationUpdatesAutomatically = false
anotherLocationManager?.delegate = self
anotherLocationManager?.desiredAccuracy = kCLLocationAccuracyBestForNavigation
anotherLocationManager?.activityType = .otherNavigation
if CLLocationManager.locationServicesEnabled() {
switch CLLocationManager.authorizationStatus() {
case .notDetermined, .restricted, .denied:
print("No access")
case .authorizedAlways, .authorizedWhenInUse:
print("Access")
}
} else {
anotherLocationManager?.requestAlwaysAuthorization()
print("Location services are not enabled")
}
anotherLocationManager?.startMonitoringSignificantLocationChanges()
}
The problem is control doesn't come into this code snippet.
if launchOptions?[UIApplication.LaunchOptionsKey.location] != nil {
//You have a location when app is in killed/ not running state
}
Related
Fist time when I install my iOS app on a device via xcode and location service is off, requestAlwaysAuthorization() shows "Turn on location services" that push user to device Settings to turn on location service, then user comes back to the app and would see "permission" alert(with three options always, while using and never). If user taps on always option then closes the app completely and turns location service off then opens app again, "Turn on location services" alert is not displayed. This is my code:
override func viewDidLoad() {
super.viewDidLoad()
locationManager = CLLocationManager()
NotificationCenter.default.addObserver(self, selector: #selector(checkLocationService), name: Notification.Name.UIApplicationWillEnterForeground, object: nil)
}
#objc func checkLocationService() {
if CLLocationManager.locationServicesEnabled() {
switch CLLocationManager.authorizationStatus() {
case .denied, .notDetermined, .restricted:
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
case .authorizedWhenInUse, .authorizedAlways:
...
}
} else {
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
}
}
I've added all three location keys in the Info.plist:
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>My app need your location to work</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>My app need your location to work</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>My app need your location to work</string>
I am testing on iOS 11 and 12 and I have no idea what is wrong.
You only request auth once... if the user grants permission then you dont need to ask again and if they refuse you can't prompt again. you need to handle it differently if denied. you push the user to the app settings in the settings menu and the user has to enable from there
switch CLLocationManager.authorizationStatus() {
case .notDetermined, .restricted:
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
case .authorizedWhenInUse, .authorizedAlways:
...
case .denied:
// present an alert advising the user that they need to go to the settings menu to enable the permissions as they have previously denied it.
// if the user presses Open Settings on the alert....
if let url = URL(string: UIApplicationOpenSettingsURLString) {
UIApplication.shared.open(url: url)
}
}
I have an iOS application developed in Swift. My app is currently in Swift 2 but I am using Xcode 8 with Swift 3. The app is configured to use the legacy swift language version.
Until recently the app was working correctly.
The app asks for the correct rights for always use the location and the autorisation is correctly set to always.
I renewed the signing identity for the production app and the app stopped to be notified on a location update but was still working in development mode (launched from xcode).
Now I revoked and renew the production and development certificate and the app does not update the location while in background whereas the autorisation is set to always.
The app is correctly installed so I guess that the certificates are okay but I don't understand why the location is not updated in background.
I run the app on an iPhone 7 with IOS 10.2 and xcode automatically manage signing.
Here is my location manager configuration:
public class LocationManager : NSObject, ModuleManager, CLLocationManagerDelegate {
/// The core location manager
let coreLocationManager: CLLocationManager
public var datas: JSONable? {
get {
return LocationDatas(locations: self.locations)
}
set {
self.locations = newValue == nil ? [Location]() : newValue as? [Location]
}
}
/// The list of locations to send
private var locations: [Location]?
/// The last location
public var lastLocation: Location? {
return self.locations?.last
}
public override init() {
self.coreLocationManager = CLLocationManager()
if #available(iOS 9.0, *) {
self.coreLocationManager.allowsBackgroundLocationUpdates = true
}
// The accuracy of the location data.
self.coreLocationManager.desiredAccuracy = kCLLocationAccuracyBest;
// The minimum distance (measured in meters) a device must move horizontally before an update event is generated.
self.coreLocationManager.distanceFilter = 500; // meters
self.locations = [Location]()
super.init()
self.coreLocationManager.delegate = self
self.locationManager(self.coreLocationManager, didChangeAuthorizationStatus: CLLocationManager.authorizationStatus())
}
// MARK: - CLLocationManager Delegate
public func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
NSLog("location update")
guard locations.count > 0 else {
NSLog("Module Location -- no location available")
return
}
// Add all location waiting in the list to send
self.locations?.appendContentsOf(locations.map { Location(cllocation: $0) })
SDKManager.manager?.sendHeartbeat()
}
public func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch CLLocationManager.authorizationStatus() {
case .NotDetermined:
if #available(iOS 8.0, *) {
self.coreLocationManager.requestAlwaysAuthorization()
} else {
self.coreLocationManager.startUpdatingLocation()
}
case .Denied, .Restricted:
NSLog("Module Location -- access denied to use the location")
case .AuthorizedAlways:
NSLog("AuthorizedAlways")
self.coreLocationManager.startUpdatingLocation()
//self.coreLocationManager.startMonitoringSignificantLocationChanges()
default:
break
}
}
public func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
NSLog("Module Location -- error : \(error)")
}
}
The locationManager function is not called in background.
Here is my info.plist:
Here is the authorization on the phone:
The little location arrow is always there but no location update is logged.
I checked your code and it seems to be fine, revise if you have done these required settings
Enable location updates in Background mode
Add NSLocationAlwaysUsageDescription in your info.plist
If you did not do 1st point you app would have crashed but if did not do 2nd point your code will go through but you will never get updates.
Update:
It seems your LocationManager object is released in ARC. Can you try changing your LocationManager class to Singleton by added
static let sharedInstance = LocationManager()
And accessing LocationManager in your code like this
LocationManager.sharedInstance
You don't need to use App background Refresh just for Location update in Background. (It can be used for other maintenance work like DB cleaning, uploading, downloading, etc. while charging)
While initializing coreLocationManager, set the following properties as well
// It will allow app running location updates in background state
coreLocationManager.allowsBackgroundLocationUpdates = true
// It will not pause location automatically, you can set it true if you require it.
coreLocationManager.pausesLocationUpdatesAutomatically = false
It is my general ViewController class , I research the this question iOS app doesn't ask for location permission , I already have NSLocationAlwaysUsageDescription and NSLocationWhenInUseUsageDescription
#IBOutlet weak var ownMapView: MKMapView!
let locationManager : CLLocationManager = CLLocationManager();
override func viewDidLoad() {
super.viewDidLoad();
self.locationManager.delegate = self;
self.locationManager.requestWhenInUseAuthorization();
self.locationManager.startUpdatingLocation();
}
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
guard status == .AuthorizedWhenInUse else
{
print("Location not using");
return;
}
print("Location using.");
ownMapView.showsUserLocation = true;
}
Even I added NSLocationAlwaysUsageDescription and NSLocationWhenInUseUsageDescription this is not working for me . It prints only "Location not using"
How do I request correctly ?
To understand this correctly check the CLLAuthorizationStatus doc:
https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/#//apple_ref/c/tdef/CLAuthorizationStatus
CLAuthorizationStatus has several possible values:
NotDetermined - User has not yet made a decision to allow/deny to use location
Restricted - App is restricted to use location
Denied - User has denied to use location.
AuthorizedAlways - This app is authorized to start location services at any time.
AuthorizedWhenInUse - App is authorized to start most location services while running in the foreground
You are checking only "AuthorizedWhenInUse" status but you launch first its status is NotDetermined.
You can further check the status like below:
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
if status == .NotDetermined
{
print ("Loction use not determined")
return
}
if status == .Denied
{
print ("Location determined")
return
}
guard status == .AuthorizedWhenInUse else
{
print("Location not using");
return;
}
print("Location using.");
ownMapView.showsUserLocation = true;
}
Have us added NSLocationAlwaysUsageDescription & NSLocationWhenInUseUsageDescription in plist? . Add these and it will work .
It seems that my app is not launched and called with location updates when it is in a terminated state.
Since it is a little bit hard for me to test what is not working (using a real device is not really easy when you have to move back and forth inside an office trying to trigger a significant location change), is there a way to simulate location changes in the simulator while the app is closed?
I have already tried using the Simulator > Debug > Location > [City Bicyce Ride, ...] but it seems that it works only when the app is running. I even tried creating a scheme where the app is not launch automatically after compiling.
Do you have any suggestion on how to debug this kind of issues?
(By now I am just logging on separate files at every application launch, even though unfortunately the app gets not launched in background when is in a closed state )
This is the code in my app delegate:
lazy var locationManagerFitness: CLLocationManager! = {
let manager = CLLocationManager()
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.distanceFilter = 1.0
manager.activityType = CLActivityType.Fitness
manager.delegate = self
manager.requestAlwaysAuthorization()
return manager
}()
func startLocationMonitoring()
{
locationManagerFitness.stopMonitoringSignificantLocationChanges()
locationManagerFitness.startUpdatingLocation()
}
func startLocationMonitoringSignificantChanges()
{
locationManagerFitness.stopUpdatingLocation()
locationManagerFitness.startMonitoringSignificantLocationChanges()
}
// MARK: - CLLocationManagerDelegate
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
{
if manager == locationManagerFitness
{
log.debug("locationManagerFitness:")
}
for newLocation in locations
{
saveLocation(newLocation)
if UIApplication.sharedApplication().applicationState == .Active {
log.debug("App is active. New location is \( newLocation )")
} else {
log.debug("App is in background. New location is \( newLocation )")
}
}
}
func saveLocation(location: CLLocation) -> Location {
let entity = NSEntityDescription.entityForName("Location",
inManagedObjectContext:managedObjectContext)
let locationCD = NSManagedObject(entity: entity!,
insertIntoManagedObjectContext: managedObjectContext) as! Location
locationCD.setValue(location.coordinate.latitude, forKey: "latitude")
locationCD.setValue(location.coordinate.longitude, forKey: "longitude")
locationCD.setValue(NSDate(), forKey: "creationDate")
do {
try managedObjectContext.save()
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
}
return locationCD
}
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?)
-> Bool {
//Logs
let documentDirectoryURL = try! NSFileManager.defaultManager().URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: true)
let dayTimePeriodFormatter = NSDateFormatter()
dayTimePeriodFormatter.dateFormat = "hh:mm_dd-MM-yyyy"
let dateString = dayTimePeriodFormatter.stringFromDate(NSDate())
let logURL = documentDirectoryURL.URLByAppendingPathComponent("log_\( dateString ).txt")
log.setup(.Debug, showThreadName: true, showLogLevel: true, showFileNames: true, showLineNumbers: true, writeToFile: logURL, fileLogLevel: .Debug)
log.debug("Starting app...")
// StatusBar
UIApplication.sharedApplication().statusBarStyle = .LightContent
switch CLLocationManager.authorizationStatus()
{
case .AuthorizedAlways:
if let _ = launchOptions?[UIApplicationLaunchOptionsLocationKey]
{
startLocationMonitoringSignificantChanges()
}
default:
break;
}
log.debug("App started!")
return true
}
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.
log.debug("startLocationMonitoringSignificantChanges")
startLocationMonitoringSignificantChanges()
}
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.
log.debug("startLocationMonitoring")
startLocationMonitoring()
}
The behavior of the above code is that the app is monitoring user location changes only when it is active.
Looking the image below is clear that the simulator seems to continue to move the location of the Bicycle Ride, however the AppDelegate CLLocationManagerDelegate's locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) is not called while the app is terminated or in background:
Did you tried the custom Location instead of City Bycle Ride? One of my app I used Region Monitoring and if I give manually the locations then it is work even if i lock the simulator.
If user has explicitly denied authorization for this application, or location services are disabled in Settings, it will return Denied status. How can I know the exact reason of it?
I've made this two function to check for each case
If user explicitly denied authorization for your app only you can check it like this,
+ (BOOL) isLocationDisableForMyAppOnly
{
if([CLLocationManager locationServicesEnabled] &&
[CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied)
{
return YES;
}
return NO;
}
If location services are disabled in Settings,
+ (BOOL) userLocationAvailable {
return [CLLocationManager locationServicesEnabled];
}
And I'm using it like this,
if([UserLocation userLocationAvailable]) {
//.... Get location.
}
else
{
if([UserLocation isLocationDisableForMyAppOnly]) {
//... Location not available. Denied accessing location.
}
else{
//... Location not available. Enable location services from settings.
}
}
P.S. UserLocation is a custom class to get user location.
Swift
Use:
CLLocationManager.authorizationStatus()
To get the authorization status. It's a CLAuthorizationStatus, you can switch on the different status' like this:
let status = CLLocationManager.authorizationStatus()
switch status {
case .authorizedAlways:
<#code#>
case .authorizedWhenInUse:
<#code#>
case .denied:
<#code#>
case .notDetermined:
<#code#>
case .restricted:
<#code#>
}
Swift:
I've made this two function to check for each case
If user explicitly denied authorization for your app only you can check it like this,
class func isLocationDisableForMyAppOnly() -> Bool {
if CLLocationManager.locationServicesEnabled() && CLLocationManager.authorizationStatus() == .denied {
return true
}
return false
}
If location services are disabled in Settings,
class func userLocationAvailable() -> Bool {
return CLLocationManager.locationServicesEnabled()
}
And I'm using it like this,
if UserLocation.userLocationAvailable() {
//.... Get location.
} else {
if UserLocation.isLocationDisableForMyAppOnly() {
//... Location not available. Denied accessing location.
} else {
//... Location not available. Enable location services from settings.
}
}
P.S. UserLocation is a custom class to get user location.
Swift 5.0 iOS 14.0
Since 'authorizationStatus()' was deprecated in iOS 14.0 you should use the instance directly.
authorizationStatus is now a property of CLLocationManager.
You can use the following example:
import CoreLocation
class LocationManager {
private let locationManager = CLLocationManager()
var locationStatus: CLAuthorizationStatus {
locationManager.authorizationStatus
}
}
Have also a look at this question: AuthorizationStatus for CLLocationManager is deprecated on iOS 14