In my iOS app I turned on location services in background and also set on always. I also set up region monitoring for every 500 meters so if iOS kills my app in background then it will wake up using region monitoring.
But I found one major issue in updating location. I disabled location services of iOS and re enable it, my app is still in background but it's location icon showing disable and other app which is not even in background shows enable. Please see attached screenshot.
If any one knows about this then please guide me.
I think you have wrong concept, the arrow unfilled means that the app is using geofences, if you scroll to the bottom in that list you can read the legend as you can se in this screenshot
Hope this helps you
In app delegate.m,
- (BOOL)application:(UIApplication )application didFinishLaunchingWithOptions:(NSDictionary )launchOptions
{
[self setUpLocationManager];
return YES;
}
#pragma mark - CLLocationManager Delegate
-(void)setUpLocationManager
{
LocationManager=[[CLLocationManager alloc]init];
LocationManager.delegate=self;
LocationManager.desiredAccuracy=kCLLocationAccuracyBest;
[LocationManager requestWhenInUseAuthorization];
[LocationManager startUpdatingLocation];
}
-(void)locationManager:(CLLocationManager )manager didUpdateLocations:(NSArray<CLLocation > *)locations
{
CLGeocoder *GioCoder=[[CLGeocoder alloc]init];
[GioCoder reverseGeocodeLocation:LocationManager.location completionHandler:^(NSArray<CLPlacemark > Nullable placemarks, NSError * Nullable error)
{
if (error !=nil)
{
}
else
{
CLPlacemark *place=[placemarks lastObject];
NSLog(#"%#",place);
NSString *Path=[[NSBundle mainBundle]pathForResource:#"countries" ofType:#"json"];
NSError *Error;
NSData *JsonData=[[NSData alloc]initWithContentsOfFile:Path options:NSDataReadingMappedAlways error:&Error];
NSMutableArray *DataArray=[[NSMutableArray alloc]init];
if (Error==nil)
{
NSError *err;
DataArray=[NSJSONSerialization JSONObjectWithData:JsonData options:NSJSONReadingMutableContainers error:&err];
}
for (int i=0; i<DataArray.count;i++)
{
if ([[[DataArray objectAtIndex:i]valueForKey:#"code"] isEqualToString:place.ISOcountryCode])
{
// Insert your code here
[LocationManager stopUpdatingLocation];
break;
}
}
}
}];
}
-(void)locationManager:(CLLocationManager )manager didFailWithError:(NSError )error
{
if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined || [CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied || [CLLocationManager authorizationStatus] == kCLAuthorizationStatusRestricted)
{
NSLog(#"Location not enabled in your device");
}
else
{
}
}
Just as Reiner said in his answer, your understanding is wrong. If you have an empty purple arrow it means your geofence is set correctly.
If another app is staying filled purple. It could be because of multiple reasons:
It has pausesLocationUpdatesAutomatically set to false and is always tracking.
It has pausesLocationUpdatesAutomatically set to true but hasn't yet paused long enough at a location to trigger a pause which would technically turn that filled purple into either gray or empty purple (which means the app is continuing tracking using significant location changes or regionMonitoring or visits monitoring)
AFAIK if you region is setup for 500meters then it's better to use SignificantLocation Changes. Because you can use cellTower information. For regionMonitoring app turns it's location Tracking on and off every few minutes to make sure it's been exited the app or not.
VERY IMPORTANT NOTE
Apple documents on this are very confusing. It took me a whole week to figure it out!
Region Monitoring or significant location Tracking or visit Monitoring will only launch app in the background if and only if your app was terminated. If your app is launched again then right from the DidFinishLaunching you can startLocationUpdates.
If your app was suspended or for some reason still in the background you would only get the didExitRegion callback. And AFIAK from the callback you're not able to startUpdateLocations. This is a very vague text from WWDC.
Finally, you must start your location updates while in the
foreground. If you don’t start your updates in the foreground, you
won’t get this special behavior. So what happens if you do start your
updates in the background? Well, first off, your app will probably
need AlwaysAuthorization since your app won’t be considered in use at
that time. Furthermore, Core Location won’t take any action to
ensure your app continues to run. So if you have background runtime
for some reason, and you decide to start a location session, you
might get some updates, but you might also get suspended before you receive all the information you had hoped to receive.
WWDC 2016: Session 716
I don't know why Apple is saying might...it could be something that varies based on battery/cell coverage/ usage patterns / iOS version. It's obviously something they don't want us developers know how. They want to force us to go for other ways.
The only way for you to get your app into terminated state is either if you user manually kills the app or you wait for a very very long time ie wait for any or all other background tasks to finish + then once app is suspended wait for it to become terminated and then exit the region to launch app and be able to start tracking Locations
Related
I am working on the iPhone app in Objective-C in which I count steps of user by using CMAccelerometerData.
It works fine when app is running in background ,
But when I kill my app (double-clicking the Home button and then swiping app away) then I can not get the steps count.
The feature of app is that fetch steps count if my app is in kill state (double-clicking the Home button and then swiping app away) and I run it from icon.
Is there any way to get steps count if our app is killed (double-clicking the Home button and then swiping app away) ?
Here is the method to get steps
- (void)startDetectionWithUpdateBlock:(void (^)(NSError *))callback
{
if (self.motionManager.isAccelerometerActive) {
return;
}
[self.motionManager startAccelerometerUpdatesToQueue:self.queue withHandler:^(CMAccelerometerData *accelerometerData, NSError *error) {
if (error) {
if (callback) {
dispatch_async(dispatch_get_main_queue(), ^{
callback (error);
});
}
return ;
}
CMAcceleration acceleration = accelerometerData.acceleration;
CGFloat strength = 1.2f;
BOOL isStep = NO;
if (fabs(acceleration.x) > strength || fabs(acceleration.y) > strength || fabs(acceleration.z) > strength) {
isStep = YES;
}
if (isStep) {
if (callback) {
dispatch_async(dispatch_get_main_queue(), ^{
callback (nil);
});
}
}
}];
}
And through this way I update steps count :
[[SOLocationManager sharedInstance] start];
[[SOStepDetector sharedInstance] startDetectionWithUpdateBlock:^(NSError *error) {
if (error) {
NSLog(#"%#", error.localizedDescription);
return;
}
self.actualCheckpoint.stepsCount ++;
NSLog(#"Total Steps: %ld",(long)self.actualCheckpoint.stepsCount);
[[NSNotificationCenter defaultCenter] postNotificationName:SBCompetitionStepsCountDidChange object:self.actualCheckpoint];
}];
you can catch the data in appDelegate's
- (void)applicationWillTerminate:(UIApplication *)application {}
You can't access HealthKit data in the background when the device is locked, according to Apple:
For security, the HealthKit store is encrypted when the device is locked. The HealthKit store can only be accessed by an authorized app. As a result, you may not be able to read data from the store when your app is launched in the background; however, apps can still write data to the store, even when the phone is locked. HealthKit temporarily caches the data and saves it to the encrypted store as soon as the phone is unlocked.
https://developer.apple.com/library/ios/documentation/HealthKit/Reference/HealthKit_Framework/
You'll need to read the data on startup to 'catch up' from the last time your app was run.
If the app is killed by the user by swiping up, rather than by simply putting the app into the background, it is no longer allows to execute any code until the user opens it again. However, there's no way to get direct access to the accelerometer if the app is completely killed.
There are ways to handle keeping the app open indefinitely if it goes into the background (not manually killed by the user). However, having the accelerometer powered up constantly would be a significant battery drain, and if all you want to do it track steps, you can simply use the HealthKit API. (If you need your app to work on old devices, you will have to resort to background processing.)
HealthKit
As an Apple OS-level API, HealthKit is always working and (if the user's device supports it and the user hasn't disabled it) is constantly tracking steps in the background, using Apple's accurate, battery efficient algorithm (it uses the device's motion coprocessor).
All you need to do is integrate your app with HealthKit such that it can read the device's Steps metric. Take a look at this objective-C tutorial.
An app I'm working on currently sets up region monitoring with the user's current location when the app is backgrounded. When the app becomes active again I am trying to stop monitoring for the region, but it seems to work intermittently with the majority of the time resulting in it failing to act as expected. When the app is backgrounded, I start monitoring for the region and it works fine when I log the details:
- (void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region {
DDLogInfo(#"CREATED REGION: %#", region.identifier);
}
Which results in the following log:
"CREATED REGION: regionFor: [real lat here, real lon here] with radius: 100"
When the app wakes up, I call the following function:
- (void)stopMonitoringAllRegions {
DDLogInfo(#"About to stop monitoring for %d regions", [locationManager monitoredRegions].count);
// stop monitoring for any and all current regions
for (CLRegion *region in [[locationManager monitoredRegions] allObjects]) {
[locationManager stopMonitoringForRegion:region];
}
DDLogInfo(#"After stopping, we're currently monitoring for %d regions", [locationManager monitoredRegions].count);
}
Which results in the following log about 75% of the time:
"About to stop monitoring for 1 regions"
"After stopping, we're currently monitoring for 1 regions"
and infrequently I get what seems like a success:
"About to stop monitoring for 1 regions"
"After stopping, we're currently monitoring for 0 regions"
I've tried a couple of things with no success. The regions I'm creating are CLCircularRegions, which inherit from CLRegion so that should work regardless, but in the for-loop I've changed CLRegion to CLCircularRegion with no effect. I was originally using [locationManager monitoredRegions] by itself, which returns an NSSet, so I thought using the allObjects function to get the array would fix the issue, but it hasn't.
I also thought it might be an issue with mutating the array while enumerating, but the only other post I saw on SO said that the above worked for them...
Am I missing something?
If you read up on monitoredRegions, it represents all monitored regions of all CLLocationManager instances, and so is probably controlled by a private dispatch queue - which would explain the delays.
My suggestion would be to keep your own mutable array (or set) around, using it to keep track of what regions are monitored and which are not, and not rely on the location manager for that collection.
Now that its clear you cannot rely on immediate changes to it, I'd design around it rather than try to find some heuristic that seems (today) to work but bites you later.
On iOS, in my application delegate I start region monitoring and as soon as I enter in a beacon region I start the ranging logic, using locationManager:didRangeBeacons:inRegion. According to the Apple documentation, this method should be called only when the the region comes within the range or out of the range or when the range changes.
My problem is that I get a call to this method every second as long as I am inside the region. How to decrease the number of calls to this method while still ranging?
locationManager:didRangeBeacons:inRegion is called once per second, no matter what. Each time it's called, the beacons parameter will contain an array of all beacons that the app can currently see, ordered by proximity. There's no way to limit the frequency at which this method is called, short of stopping ranging.
When monitoring regions (instead of ranging), your app will have didEnterRegion: and didExitRegion called, along with didDetermineState:. See this answer for a little more detail.
According to the Docs:
"The location manager calls this method whenever a beacon comes within range or goes out of range. The location manager also calls this method when the range of the beacon changes; for example, when the beacon gets closer."
Whats probably happening is the range is changing slightly which is causing the behaviour you describe.
Why is this a problem
EDIT:
IN the background you will get notified of entering regions via the app delegate method:
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region{}
You can use this to determine the state:
if(state == CLRegionStateInside)
{
//Inside a region:
}
else if(state == CLRegionStateOutside)
{
//Outside a region
}
else {
//Something else
}
You can use this to gather a limited amount of information or prompt the user to load the application via a local notification. When your app resumes you can then gather more information via the locationManager.
At the bottom (in AppDelegate.m) you can see my CLLocationManager delegate methods, none of them get called. I'm using a GPX file, but even if the regions do not get entered or exited, the delegate method didStartMonitoringForRegion should be called.
SomeOtherClass.m
AppDelegate appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate.locationManager startMonitoringForRegion:regionToMonitor desiredAccuracy:kCLLocationAccuracyNearestTenMeters];
AppDelegate.h
#interface AppDelegate : UIResponder <UIApplicationDelegate,CLLocationManagerDelegate>
#property (nonatomic, retain) CLLocationManager *locationManager;
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
if (locationManager==nil) {
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
}
return YES;
}
-(void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {
NSLog(#"Did enter region");
}
-(void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region {
NSLog(#"Did exit region");
}
-(void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
NSLog(#"Fail");
NSLog(#"%#", [error description]);
}
-(void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region {
NSLog(#"Did start monitoring for region: %#", region.identifier);
}
First of all you should add the following Location manager delegate method, and see if for some reason the region monitoring registration failed:
- (void)locationManager:(CLLocationManager *)manager monitoringDidFailForRegion:(CLRegion *)region withError:(NSError *)error {
NSLog(#"%#",error);
}
Second, the region monitoring is a system shared resource.
The documentation states that it allows you limited number of regions to monitor (not specifying any number unfortunately) and mentioning that if another app register additional regions to monitor, some of your apps monitored regions, might be discarded.
Third, region monitoring is not using any GPS technology. It only uses the cellular antenna of your network operator and whenever you change a cell tower, it fires an system event that loops through all monitored regions and see if a region is within the new area you are now located.
This means that you should expect less accuracy in the service and therefore you should increase the radius you are setting for a region.
Finally if your app is completely terminated and NOT suspended, then your app will receive in the app delegate inside the:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
a UIApplicationLaunchOptionsLocationKey in the launchOptions dictionary.
It's your responsibility then to reinitialize your location manager, get the current location and fire a local push notification to the user, within the limited time your application live state has.
As the app is not going to run normally at that stage, but in a limited background mode state.
Also if you want to test the app in the simulator, you should add a track.gpx file to your project and set the simulator to track mode.
Make sure that the 2 locations you put in the simulator are not very distant (as it might take quite long time for the tracking to complete) and set them in a path, where it will enter your monitored region.
Then see if you get any callbacks.
Do not test it with your device, as you need to actually walk a couple of blocks to see any real interaction with the device :-)
Not entirely sure why it wouldn't receive your callbacks. I'll cover a few things I'm noticing and you can see if they help.
You are not setting the desiredAccuracy or distanceFilter in your location manager. They should default to something, but if your locations in the GPX aren't within the accuracy of the regions, it could just be not getting close enough to trigger.
The method you are using to start monitoring has been deprecated in iOS 6. You can add the accuracy to the location manager and leave that off your call.
It would be helpful to see how you are creating your CLRegion to monitor, regionToMonitor. If it is being created ok as soon as you start monitoring, you should see the hollow purple location arrow show up. You should also receive the delegate call -didStartMonitoringForRegion. If neither of these are showing up, then you probably just have an issue with your location manager setup.
One suggestion would be to create your own location manager class and turn it into a singleton. This will prevent you from accidentally initializing multiple delegates and getting multiple calls. It also gives you one clean class to contain all your callback methods.
I don't see anything wrong with the code you've included, so I'm guessing the problem lies in the code you haven't included. Check to make sure your location manager code is being initialized and make sure your CLRegion is being created correctly. Hope this helps. I'll be happy to update my answer if you include more code and we find out what the true issue is.
I have location processing newly implemented in my app, testing in foreground and background with satisfactory results. The application is monitoring significant location changes as well as several regions. I have not yet figured out if I will get the same results when the app is suspended or terminated.
As I understand it, when the app is woken from these states it will be as if the app just started except the launchOptionsLocation key will be found in the launchOptions Dictionary param. My question is, can I allow the app delegate to proceed as normal and assume all is well? Is it necessary to intercept all the view setup code?
The very first lines in my didFinishLaunchingWithOptions:launchOptions method are:
NSManagedObjectContext *context = [self managedObjectContext];
if (!context) {
//Handle Error
}
self.sharedLocationHandler = [[[TTLocationHandler alloc] init] autorelease];
self.siteLogger = [[[ProjectSiteLogger alloc] initWithOptions:nil] autorelease];
self.siteLogger.locationHandler = self.sharedLocationHandler;
self.siteLogger.managedObjectContext = context;
In all likelihood, this covers all that I need in order to respond to the location event. I could easily test for the location key in launchOptions and skip the entire remainder of the method, though I am not sure what unforeseen complications that may entail.
I also question what would result then if a user happened to start the app intentionally while it was in that incomplete state having no views set up at all.
Is this something that has been tried, is it even necessary at all? I don't see how to test this as I don't know of a way to stay connected to Xcode debugger when the app is suspended.
---Additional Updated Info----
Initial testing on a day of carrying test phone around, my location processing seemed to do all the tasks I wanted it to with no changes to appDelegate. So I presume that wake from suspend/term came up and executed the full appDelegate procedure even though no view controllers ever became visible.
So, while it seems that it is not required to alter the startup procedures, may there still be a performance or battery concern that would make it prudent to cut the appDelegate procedure short and minimize processing?
After a good bit of testing and tweaking I find:
The app will wake from inactive or termed with no issues.
My location methods ran and completed regardless of the changes to the App Delegate.
When I cut the app delegate processes short, I did have intermittent start issues as anticipated.
Even though there were no apparent performance benefits, I went ahead and separated out the view setup code and added a flag so that it would not be run unless the application was being presented in the foreground. It just seems right to run no more processing than needed.
The code I ended up with is:
// Initialize a flag
BOOL needsViewsSetup = YES;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
// All my response to location events are handled in the these two classes
self.sharedLocationHandler = [[[TTLocationHandler alloc] init] autorelease];
self.siteLogger = [[[ProjectSiteLogger alloc] initWithOptions:nil] autorelease];
self.siteLogger.locationHandler = self.sharedLocationHandler;
self.siteLogger.managedObjectContext = self.managedObjectContext;
// check launchOptions, skip all the views if there we were woken by location event
if (![launchOptions objectForKey:UIApplicationLaunchOptionsLocationKey]) {
[self confirmDataExistsAtStartup];
[self setupTabbarWithViews];
}
return YES;
}
Then you have the views setup:
-(void)setupTabbarsWithViews
{
// Code to setup initial views here
// ending with flag toggle to prevent repeat processing
needsViewsSetup = NO;
}
And in application will enter foreground:
- (void)applicationWillEnterForeground:(UIApplication *)application
{
if (needsViewsSetup) {
[self setupTabbarWithViews];
}
}
Note: My application is not running location services in the background, only listening for significant location changes and geofence.