Currently in this iOS application I have added some controller I've written as an observer to the [NSNotificationCenter defaultCenter] to observe NSUserDefaultsDidChangeNotification.
- (void)startObservingDefaults
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(userDefaultsDidChange)
name:NSUserDefaultsDidChangeNotification
object:nil];
}
The user default that I am watching should enable or disable a CLLocationManager using a PSToggleSwitchSpecifier from the settings menu. After starting the application and sending startUpdatingLocation to a CLLocationManager ivar I can go to the settings menu and disable the location manager. My selector userDefaultsDidChange will then check if that default is enabled or disabled. If it is enabled, I send startUpdatingLocation to the location manager instance, if it is disabled I send stopUpdatingLocation to it. Each path logs its action like so
- (void)userDefaultsDidChange
{
if ([self duringOperationHours] && [self isEnabled]) {
NSLog(#"\n\n\nWithin Operating Hours.\n\n\n");
[_locationManager startUpdatingLocation];
NSLog(#"Enabled Location Manager.");
} else {
NSLog(#"\n\n\nDisabled or Outside of Operating Hours.\n\n\n");
[_locationManager stopUpdatingLocation];
NSLog(#"Disabled Location Manager.");
}
}
I have other settings that control time of day (start time and end time ex. 6AM to 6PM). If I change one of those settings and the current time is still within that time range the log will show that it is still "Within Operating Hours." and that the location manager is enabled.
If I change it so that the location manager gets disabled, it does log both NSLog statements in my method above and it DOES shut down the location manager.
The problem comes when I try to start it back up. So say I am in the settings and it is enabled. I disable it, the log shows it is disabled and the little icon for GPS disappears, if I then re-enable it the notification doesn't fire at all. In fact, no other settings even in other child panes don't fire until I go back to the application. (I've tried a dispatch_async inside the userDefaultsDidChange method to both main_queue and global_queue and saw no change).
MY QUESTION IS am I doing something wrong that might cause this to lock up with the location manager? Or is this some internal queue issue to CLLocationManager? Or something else? Perhaps CLLocationManager will only start if the application is in the foreground? Even if that were the case I should still be seeing the log statements that it at least attempted to start the location manager right?
Nevermind I figured it out.
The observation of the NSNotificationCenter on the NSUserDefaultsDidChangeNotification DOES NOT wake up the application. What was happening was the CLLocationManagerDelegate protocol applied to my controller would "wake up" the application to do what it needed to the with CLLocations that were received. Upon this waking up, the observation of change to some user defaults in the NSNotificationCenter would fire off my selector userDefaultsDidChange.
As long as the CLLocationManager was updating and there to alert the application it would allow the controller to call the designated selector. When the CLLocationManager was no longer updating the location the application would not be woken up and therefore not know any user defaults had changed until the app returned to the foreground.
I will update this answer if I come up with a better solution for this odd observation scenario.
Related
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 want to attach a listener in IOS which is invoked whenever GPS is turned ON or OFF from settings. Listener should be invoked no matter my application is running, in background or has been stopped. I have this functionality that i need to keep GPS settings of a user who has installed app, on server side, so whenever GPS is changed i must notify the server.
I guess there isn't any listener,
You can Use Delegate method of CLLocationManager.
Use this delegate method for getting location - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
Form this [locations lastObject] you can latest location send location to server.
You can not get location when app is stopped.
As of now, there is no such notification exists.
However you can create and start a timer (NSTimer) that will regularly poll whether GPS is enabled or not, using 'CLLocationManager locationServicesEnabled' method.
Within your class, you can have a bool iVar that will be set / reset based on return value. Whenever its value is altered, you can notify server about service start / stoppage.
I have an iOS app that uses the CLLocationManager to monitor regions and to get GPS updates. Most of the time, I want my app to continue tracking the cellphone when it goes in background or even when it gets killed, and it works well (I can still see the small arrow in the status bar after my app gets killed). The problem is that I am not able to stop monitoring the regions and GPS updates after my app has been restarted by the Location Services.
When my app gets restarted by the Location Services, I instanciate the CLLocationManager and then call its methods stopRangingBeaconsInRegion and stopUpdatingLocation before setting its delegate to nil and itself to nil.
Thanks to NSLogger, I can see that my callbacks are no longer called, but the small arrow in the status bar stays there (and my app is the only one that I allowed to use the Location Services from the settings menu).
What did I miss? Is there a way to know what still uses the Location Services in my app?
When you call stopRangingBeaconsInRegion, where are you getting the list of regions? The proper way to do this is like below:
for (CLRegion *monitored in [self.locationManager monitoredRegions]) {
NSLog(#"Stopping monitoring on: %#", monitored.identifier);
[self.monitoringLocationManager stopMonitoringForRegion:monitored];
}
for (CLBeaconRegion *region in [self.locationManager rangedRegions]) {
NSLog(#"Stopping ranging on: %# ", region.identifier);
[self.rangingLocationManager stopRangingBeaconsInRegion:region];
}
I finally found that I missed to remove some of my numerous regions. The easy way to avoid this mistake is to retrieve the list of regions monitored with the property monitoredRegions and call stopRangingBeaconsInRegion for each of them. I also forgot to call stopMonitoringSignificantLocationChanges (I didn't know that my app was using it, since I am modifying the app of a former colleague).
How do you know that it is your app that is using the location services?
The small arrow appears if the iOS itself is using the location services in the background.
if you did call stopRangingBeaconsInRegion and stopUpdatingLocation before setting the delegate to nil and you are not getting any callbacks then your app is not using the location services.
I might be confusing how region monitoring works, but this is what i have so far:
I am registering a region to monitor through my location manager, which is implemented on a singleton class, this singleton is also set as the delegate of the location manager so the implemented method is being called.
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
This works totally as expected, if the app is active or suspended the method is being called. It also makes total sense because the class has been already loaded and when the region enter event occurs iOS sends this even to my app which calls the location manager who registered (probably has a reference to it) and in turn it calls whatever delegate was also registered along it (since the class is there ready and loaded).
The issue is, what happens when the app has been killed? Is it first launched into the background? How does iOS know what delegate method to call, and if it has already been loaded?
When your app has been killed and gets started for a location update there can't be a location manager delegate yet and as such there are no notifications delivered to that delegate. The system can't know which of your classes should be used as a location manager delegate or how to instantiate it.
Instead your application:didFinishLaunchingWithOptions: gets called as usual, but the UIApplicationLaunchOptionsLocationKey is set in the options dictionary. That tells your app that you need to instantiate a location manager and set it's delegate. Only after you did this the delegate gets called with the region updates.