I am in the process of testing location updates via the background APIs that relaunch an app. I have successfully tested visits and regions and am using this same app to also test significantLocationChanges (SLC). I run my app, press home twice then swipe it off to kill it. Then I drive around. I can observe the app background activity without touching the phone via a server where (see code below) the singleton method sendBackgroundTestRecord uploads simple status messages to the server.
When I test visits and regions I have no problems. However when I test SLC, nothing happens until I turn the phone's screen on (pressing the power or home button once -- not re-running the app, or even unlocking the phone). I can drive a far distance without anything happening. I suspect the app has received an SLC event sometime during that drive because as soon as I turn the phone on for a about 2 seconds, the code below executes and I can observe the messages on the server.
I believe this is not just a network connectivity delay because the sendBackgrounTestRecord method uses a time stamp. The records on the server show a time stamp of when I turned the screen on, not when the SLC event is suspected to have fired.
I never have this problem with visits or regions, only SLC. By the way, the code has gone through many changes trying to chase this down. The latest attempt (code below) was to put everything in the appDelegate.
Any ideas?
Thanks in advance for any ideas whatsoever!
EDIT: I added beginBackgroundTaskWithExpirationHandler, which was in my original tests too.
// AppDelegate.h
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
#interface AppDelegate : UIResponder <UIApplicationDelegate, CLLocationManagerDelegate>
#property (strong, nonatomic) UIWindow *window;
#property (strong, nonatomic) CLLocationManager *locationManager;
#end
// AppDelegate.m
#import "AppDelegate.h"
#import "clsCommon.h"
#interface AppDelegate ()
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//Ask iOS for more time to process - before we do anything else
[clsCommon sharedInstance].backgroundTaskID = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{}];
[[clsCommon sharedInstance] sendBackgroundTestRecord:#"didFinishLaunchingWithOptions"];
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
if([self.locationManager respondsToSelector:#selector(setAllowsBackgroundLocationUpdates:)]) {
[self.locationManager setAllowsBackgroundLocationUpdates:YES];
}
self.locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
self.locationManager.distanceFilter = kCLDistanceFilterNone;
self.locationManager.activityType = CLActivityTypeAutomotiveNavigation;
self.locationManager.allowsBackgroundLocationUpdates = YES;
self.locationManager.pausesLocationUpdatesAutomatically = NO;
if([self.locationManager respondsToSelector:#selector(requestAlwaysAuthorization)]) {
[self.locationManager requestAlwaysAuthorization];
}
[self.locationManager startMonitoringSignificantLocationChanges];
return YES;
}
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
[[clsCommon sharedInstance] sendBackgroundTestRecord:#"didUpdateLocations"];
CLLocation * currentLocation = [locations objectAtIndex:0];
if(currentLocation !=nil) {
[[clsCommon sharedInstance] sendBackgroundTestRecord:#"didUpdateLocations curLoc not nill"];
}
}
#end
Well, knowing how aggressively iOS resolves all tradeoffs in favor of battery life I would not be surprised very much. For example, behavior of "breadcrumbs" app (set 100m geofence at your current location, move that geofence to new location on exit event) is very different depending on whether iPhone sleeps all the time in your pocket, or you check lock screen occasionally.
However, running an app very similar to yours while traveling back home yesterday did generate location updates for me.
Distances between SLC readings were in 4 to 14 km range, the smallest time gap between two consequent readings - 14 minutes. The app is very similar to yours, it does not include any networking part, just logging events to a local file (NSLog via stderr redirection). Also, I do not configure properties, which are irrelevant for SLC in location manager, like desiredAccuracy, distanceFIlter, activityType. I suggest you start from barebone app and watch for the improvement, which breaks it.
One thing to check is if your app crashes when handling location updates. If it happens in background you will not notice this unless you run logging or check crash logs via Settings.app.
Put some distance in self.locationManager.distanceFilterand is better for battery change self.locationManager.pausesLocationUpdatesAutomatically=YES;
Start Location Manager in iOS 7 from background task
Related
We implemented a functionality that tracks the user location on the background at least every 15 minutes. At the moment the background tracking works great for a couple of hours, some cases up until 8 hours, and other cases can go up to 6 days.
The issue is that at a certain point while the user is stationary the app stops managing the location updates for no apparent reason, and reopening the application will not resume the tracking process. We know that the app is not managing the location updates because the device logs show an event from locationd sending the position to our app, but there's no code execution.
We understand that the OS at any time can terminate the application, but we would like to be able to manage those cases to correctly communicate this behavior to the user that expects tracking to keep functioning or for the tracking to continue sending locations in the background.
This issue seems to be inconsistent across our user base, we've identified that users with iOS 14.4+ and from the iPhone 8 up to the iPhone 13 present this problem.
Our current configuration for the location manager is:
AppDelegate.m
#implementation AppDelegate
Location *geoData;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
///...
if (geoData == nil) {
geoData = [Location sharedInstance];
}
///...
}
- (void)applicationWillTerminate:(UIApplication *)application{
geoData = nil;
}
Location.h
#property (nonatomic, retain) CLLocationManager *locationManager;
Location.m
_locationManager = [[CLLocationManager alloc] init];
[_locationManager setDelegate:self];
if ([_locationManager respondsToSelector:#selector(requestAlwaysAuthorization)])
{
[_locationManager requestAlwaysAuthorization];
}
if ([_locationManager respondsToSelector:#selector(allowsBackgroundLocationUpdates)]) {
_locationManager.allowsBackgroundLocationUpdates = YES;
}
_locationManager.pausesLocationUpdatesAutomatically = NO
_locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
_locationManager.activityType = CLActivityTypeAutomotiveNavigation;
[_locationManager startUpdatingLocation];
We receive the updates through the didUpdateLocations where we send the data to an external API server.
The app is configured with Background fetch and Location updates background modes.
During requesting for Always permission on iOS13, the user can tap "Allow Once" which will call appropriate delegate with status kCLAuthorizationStatusAuthorizedWhenInUse but requesting for "Always" again calls delegate with kCLAuthorizationStatusAuthorizedAlways. Why? When other combinations work only once like you request always, you get it and even calling again will not call delegate with status.
Sample code to test:
#import CoreLocation;
#interface ViewController () <CLLocationManagerDelegate>
#property (strong, nonatomic) CLLocationManager *locationManager;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
}
- (IBAction)doauthloc:(id)sender {
[self.locationManager requestAlwaysAuthorization];
}
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
switch(status) {
case kCLAuthorizationStatusNotDetermined:NSLog(#"AUTH STATUS:kCLAuthorizationStatusNotDetermined"); break;
case kCLAuthorizationStatusRestricted:NSLog(#"AUTH STATUS:kCLAuthorizationStatusRestricted"); break;
case kCLAuthorizationStatusDenied:NSLog(#"AUTH STATUS:kCLAuthorizationStatusDenied"); break;
case kCLAuthorizationStatusAuthorizedAlways:NSLog(#"AUTH STATUS:kCLAuthorizationStatusAuthorizedAlways"); break;
case kCLAuthorizationStatusAuthorizedWhenInUse:NSLog(#"AUTH STATUS:kCLAuthorizationStatusAuthorizedWhenInUse"); break;
};
}
#end
It's a little confusing, isn't it? When you ask for Always and the user taps Allow Once, you are told that you got WhenInUse. But that doesn't actually matter. You have provisional Always. So:
When you subsequently go into the background and start monitoring visits or regions or whatever your location monitoring usage is, this will be converted into Always authorization for purposes of usage. (Your logging should confirm this.)
And then, because you got only Once authorization, when you come back to the foreground you will be Not Determined again.
So the takeaway is, just laugh an evil laugh and move on. Your background location monitoring will work, and that's all that matters. Not only does it work, but as a bonus you get to present the user with the authorization alert again, which is the reason for all this change in iOS 13. Don't worry, be happy.
I'm developing a location tracking app. The user can start a tracking, do whatever he wants (put the app in the background, lock the phone, etc), go back to the app and stop the tracking.
I'd like the tracking to restart if the app is killed (by the system or the user). To do so, I saw in the doc that I have to use the significant location change service, but this service does not send enough locations. Is it possible to restart the standard location service when the app is restarted thanks to the significant location change service? Or would the app be rejected?
Following methods would wake your application up even if is killed in the background:
Region events
Visit events
Significant location events
Your application will be launched with location key in the applicationDidFinishLaunchingWithOptions method. Here is an example on how to handle it gracefully (from http://nshipster.com/launch-options/):
// .h
#import CoreLocation;
#interface AppDelegate () <CLLocationManagerDelegate>
#property (readwrite, nonatomic, strong) CLLocationManager *locationManager;
#end
// .m
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// ...
if (![CLLocationManager locationServicesEnabled]) {
[[[UIAlertView alloc] initWithTitle:NSLocalizedString(#"Location Services Disabled", nil)
message:NSLocalizedString(#"You currently have all location services for this device disabled. If you proceed, you will be asked to confirm whether location services should be reenabled.", nil)
delegate:nil
cancelButtonTitle:NSLocalizedString(#"OK", nil)
otherButtonTitles:nil] show];
} else {
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
[self.locationManager startMonitoringSignificantLocationChanges];
}
if (launchOptions[UIApplicationLaunchOptionsLocationKey]) {
[self.locationManager startUpdatingLocation];
}
}
Also, you are not limited to using Significant location monitoring or region monitoring etc. You can use different mechanisms for different use cases. E.g. You can use region monitoring to setup a fallback region around the user and everytime an exit event happens, your application will be woken up with location key. You can then restart location services.
I want to make a simple IOS 6.0 application that shows the lat/lon on screen each time the location is changed. The ViewController files are pretty trivial.
I alloc a CCLocationManager objec, set its variables and start it.
didUpdateLocations is called once or twice and then it stops being fired, even if the iPhone is moved. If I press Home button and reopen the app, the data on screen is updated once or twice before it stops again.
At simulation it works fine but not on a real 3GS iPhone.
If I uncomment the start/stop inside didUpdateLocations and continuously stop and start the service, it works, but the battery gets drained in extreme rates.
Also, this is part of a much bigger project and didUpdateLocations must be called each time the location is changed.
ViewController.h
#import <CoreLocation/CoreLocation.h>
#interface ViewController : UIViewController
#property (nonatomic , strong) CLLocationManager *locationManager;
#property (weak, nonatomic) IBOutlet UILabel *lbl;
#end
ViewConroller.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;
_locationManager.distanceFilter = kCLHeadingFilterNone; // whenever we move
_locationManager.desiredAccuracy = kCLLocationAccuracyBest;
_locationManager.PausesLocationUpdatesAutomatically = NO;
[_locationManager startUpdatingLocation];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
CLLocation *newLocation = [locations lastObject];
_lbl.text=[NSString stringWithFormat:#"%lf %lf",newLocation.coordinate.latitude, newLocation.coordinate.longitude];
//[_locationManager stopUpdatingLocation];
//[_locationManager startUpdatingLocation];
}
#end
If there's any advice on what's wrong, I would welcome it, I have lost a week already without solving it.
BTW, I have tested various values for the _locationManager variables but without any chnage in the behaviour
Additional info:
application is authorized to use location services
application is in foreground
Few tips for similar issues as per my experience with location-based programming on iOS.
If it works on simulator, then it should work on actual device as
well.
Unlike simulator, the actual device will only give update when its receives new information from GPS.
There are many cases where you won't get enough/required updates on a device, like when you are in a building (or covered area)
Try testing the app in open space (GPS works much better in open space rather than room/house/large-building).
PS. I don't know how much you moved but just to let you know, moving few steps doesn't make sure you will get a new location update, try using it on some vehicle (bike/car/bus).
I had this problem too — the easiest fix is simply to duplicate the target. You can then delete the original target.
It seems the problem occurs with older Xcode projects, not newly created ones.
You can add this call to your code:
locationManager.stopUpdatingLocation()
I am using a custom BeaconManager delegate so that beacon ranging is not determined by the life-cycle of the view controller. Everything works great but every once in a while (1-2 days) beacon ranging will stop working and didRangeBeacons will never get called. The only way to fix this is for me to reset my iPhone, once I do this, it works perfectly. Below is the code that I am using. The basic flow is that when my ViewController calls ViewDidLoad it sends a notification back to the AppDelegate to tell it to start ranging for beacons, I never tell it to stop then because I want it to continue to range for beacons no matter where the user navigates to in the app. I'm wondering if my code is causing this or if this is just a bug with Bluetooth. Thanks for your help!
BeaconManager.m
#import "BeaconManager.h"
#import "AppDelegate.h"
#interface BeaconManager()<CLLocationManagerDelegate>
#property (nonatomic, strong) CLLocationManager *locationManager;
#property (nonatomic, strong) CLBeaconRegion *beaconRegion;
#end
#implementation BeaconManager
+ (id)sharedManager
{
static BeaconManager *sharedBeaconManager = nil;
static dispatch_once_t once;
dispatch_once(&once, ^{
sharedBeaconManager = [[self alloc] init];
});
return sharedBeaconManager;
}
- (id)init
{
self = [super init];
if(self)
{
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
}
return self;
}
- (void)startBeaconMonitoring:(NSString*)forUUID
{
NSUUID * uuid = [[NSUUID alloc] initWithUUIDString:forUUID];
self.beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:uuid identifier:#"com.beacons.publicRegion"];
[self.locationManager startMonitoringForRegion:self.beaconRegion];
[self.locationManager startRangingBeaconsInRegion:self.beaconRegion];
}
- (void)stopBeaconMonitoring
{
//Stop the region monitoring
if(self.locationManager != nil && self.beaconRegion != nil) {
[self.locationManager stopRangingBeaconsInRegion:self.beaconRegion];
}
}
#pragma mark - CLLocationManagerDelegate
- (void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region
{
self.beacons = beacons;
if(self.delegate != nil) {
[self.delegate beaconManager:self didRangeBeacons:self.beacons];
}
}
#end
ViewController.m
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] postNotificationName:#"startRanging" object:nil userInfo:nil];
}
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(startRangingForZombies) name:#"startRanging" object: nil];
return YES;
}
- (void)startRanging
{
//Start the beacon region monitoring when the controller loads
BeaconManager *beaconManager = [BeaconManager sharedManager];
beaconManager.delegate = self;
[beaconManager startBeaconMonitoring:#"1234-54324-34242-34242-43243"];
}
We have received many reports at Radius Networks of phones stopping detecting iBeacons and requiring a reboot or turning Bluetooth off and back on again to resolve the situation. Folks have reported this on iPhone 4S, iPhone 5s, iPhone 5c and iPads.
I do not have any hard evidence that this is something that broke as of iOS 7.1, but the report frequency has gone way up since its release. The circumstantial evidence is therefore pretty strong.
When this phone gets into this state, the phone can still scan for bluetooth devices, and can still transmit as an iBeacon. It is therefore not a hardware problem with Bluetooth. Based on the available evidence, it is most likely a newly introduced bug in CoreLocation.
Actually, it's a known bug in iOS 7.1. It's strictly a software problem with the Bluetooth stack for the latest version of iOS. Bluetooth devices detection sometimes just stops working - unfortunately, it's the case for all iOS 7.1-compatible devices. The bug has already been reported to Apple, but as long as they do not issue a fix for that, the best solution is to just reboot the device.
If reboot doesn't help, there is handy guide at SmartRobotic on how to solve it: http://www.smartbotics.com/#!4-Tips-to-Fix-Bluetooth-Problems-After-iOS-71-Upgrade/c118r/031A86F6-C8E8-4768-B4FD-E6F83D9E4317
If you are experiencing Bluetooth connectivity issues after upgrading to iOS 7.1, give these 4 tips a try.
Shut down and restart - some people have reported that this is all that was needed to fix their device after the iOS 7.1 upgrade.
Toggle Bluetooth OFF and back ON - Swipe up to access Control Center and tap the Bluetooth icon, wait at least 30 seconds, then toggle it back on. This can often repair a device which is experiencing connection issues.
Kill (force quit) the offending app - Start by double clicking Home to launch the multitasking cards interface. Touch and hold the card for the app, then toss it up and away. This will force the app to quit and it will be fully reloaded the next time the app is opened.
Clear and reset your Bluetooth device pairing - Go to Settings>Bluetooth and tap on the (i) icon for the offending device. Tap on Forget this Device. Now you should be able to re-add and re-pair the Bluetooth hardware to your device. To clear all paired devices, go to Settings > General > Reset > Reset Network Settings and then set up your Bluetooth pairings again.
Hopefully these suggestions will resolve your Bluetooth connectivity problems.
Cheers.
Apple introduced important changes to Bluetooth LE in 7.1 but also broke something inside.
From my experiments:
iPhone 4S 7.x - iBeacons work like a charm
2 x iPhone 4S 7.1, 2 x iPhone 5 7.1 - works fine but require restarts from time to time (undeterministic).
It looks like system issue - a big one.
I have contacted Estimote - they know about it.
Interesting fact: You can't find beacons - even estimote demo app can't, delegate methods are not called but You can turn (broken)device into a beacon and it will be discovered by other devices.
Disclaimer: I currently work for sensorberg, we´re selling beacons and a SDK.
We´ve hat numerous reports of this bug as well. We asked all our customers to file a bug report with Apple. Here is a template that you can use: https://gist.github.com/anonymous/5283b6941e1f7d4e4461
I personally had the behaviour twice, once I was able to record it: https://www.youtube.com/watch?v=6a6IJzaxxJg Only restarting the device helped.
Continue to file Apple rdar bug reports!
I had the similar issue, but after some time of investigation I realized that it is wrong to call the startMonitoringForRegion(region) and startRangingBeaconsInRegion(region) after each other. That is what you (Patrick) do:
- (void)startBeaconMonitoring:(NSString*)forUUID {
NSUUID * uuid = [[NSUUID alloc] initWithUUIDString:forUUID];
self.beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:uuid identifier:#"com.beacons.publicRegion"];
[self.locationManager startMonitoringForRegion:self.beaconRegion];
[self.locationManager startRangingBeaconsInRegion:self.beaconRegion];
}
Instead the startRangingBeaconsInRegion(region) should to be called in the locationManagerDelegate method locationManager(manager: CLLocationManager!, didDetermineState state: CLRegionState, forRegion region: CLRegion!) (In this case the Swift code). This was my solution.
It make sense, because the first step is to find any beacon in the region, the second one is to get specific information from the already monitored beacon.