I've been trying to use startMonitoringForRegion for while, but experiencing problems to capture enter/exit events. When I launch the app on simulator and moved to the location I specified, I get 1 enter event, but enter events never triggered again. Can somebody let me know if I'm doing correctly?
test.h
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
#interface EWViewController : UIViewController<CLLocationManagerDelegate>
{
CLLocationManager *locman;
}
#end
test.m
- (void)viewDidLoad
{
if(locman == nil)
locman = [[CLLocationManager alloc]init];
locman.delegate = self;
CLLocationCoordinate2D coord = CLLocationCoordinate2DMake(37.787359, -122.408227);
CLRegion *region = [[CLRegion alloc]initCircularRegionWithCenter:coord radius:1000.0 identifier:#"SF"];
[locman startMonitoringForRegion:region desiredAccuracy:kCLLocationAccuracyKilometer];
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void) locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
NSLog(#"ENTER");
}
- (void) locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
{
NSLog(#"EXIT");
}
I have been using region monitoring for a geofence feature in my app for about 6 months and I have found it to be very precise. Once you have everything wired up correctly, it can be used to track enter and exit events quite well.
While you can get even better and more precise readings from -didUpdateToLocation, you will have to trade off battery life to get it. If you only need occasional location updates, it should be fine. If you need constant monitoring for specific locations, region monitoring is the way to go.
I have found that -startMonitoringForSignificantLocation is not accurate at all and not very practical. It relies solely on cell tower transitions and triangulation. It also can't be used to test in the simulator for this very reason. Hope some of this information helps you out.
Yes it works, but it is very very unprecised for the current moment. (I tested it in Russia)
I recommend you using this instead:
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation;
Related
I am using following apple cllocation manager region monitoring methods:
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLCircularRegion *)region
{
}
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLCircularRegion *)region
{
}
the problem is that these method does not get called when application is in suspended state and not running.Any help would be appreciated. thanks
Try to use significant location change instead of start updating location.Also there is a limit of radius to monitor ,minimum radius should be 100 m
I write an app that uses beacons and everything is working fine when the app is in the foreground. However when I press the power button and dim the screen the app is not finding any beacons anymore. The app is still hitting the:
- (void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)clBeacons inRegion:(CLBeaconRegion *)region
method, but the logger tells me what 0 beacons has been found, which is weird, because due to the documentation, this methods should be called only when there are any beacons in range:
Tells the delegate that one or more beacons are in range.
I already have kCLAuthorizationStatusAuthorizedAlways permission from user (it's added to .plist file as well) and I have added these capabilities to the project:
This is my code for ranging beacons:
- (instancetype)init {
self = [super init];
if (self) {
NSUUID *proximityUUID = [[NSUUID alloc] initWithUUIDString:[Registry environment].config.beaconUuid];
self.region = [[CLBeaconRegion alloc] initWithProximityUUID:proximityUUID identifier:proximityUUID.UUIDString];
self.locationManager = [CLLocationManager new];
self.locationManager.delegate = self;
}
return self;
}
- (void)stopMonitoringBeacons {
[self.locationManager stopMonitoringForRegion:self.region];
DDLogInfo(#"Stopped monitoring beacons.");
}
- (void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region {
[manager requestStateForRegion:region];
[self startRangingBeacons];
}
- (void)startRangingBeacons {
[self.locationManager startRangingBeaconsInRegion:self.region];
DDLogInfo(#"Started ranging beacons.");
}
- (void)locationManager:(CLLocationManager *)manager rangingBeaconsDidFailForRegion:(CLBeaconRegion *)region withError:(NSError *)error {
DDLogInfo(#"Beacon ranging failed with error: %#.", error.localizedDescription);
}
- (void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)clBeacons inRegion:(CLBeaconRegion *)region {
DDLogDebug(#"Found %tu beacons.", clBeacons.count);
// call the server (no worries, not all the time)
}
Any idea what am I missing? Or maybe I can't trust these logs?
SOLVED:
The problem was the way I've tested it. As #davidgyoung mentioned, ranging works only in foreground and if we want to run it in background we need to start it from monitoring, which informs us about new beacons in background. I already did it in my code:
- (void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region {
[manager requestStateForRegion:region];
[self startRangingBeacons];
}
But this method is called only if we meet new beacon. I was already simulating beacon when my app was in foreground, so when I dimmed the screen with power button, ranging was stopped and monitoring was never called - I would have to turn the beacon off/on or walk out/in of the beacon range to call this method again.
Beacon ranging on iOS generally only works in the foreground, and for about 10 seconds after your app first moves to the background. Beacon monitoring on the other hand can work in the background and wake your app up when a beacon is detected. If you do both ranging and monitoring simultaneously, you will again get 10 seconds of background ranging after a monitoring trigger wakes your app up in the background.
You can extend background ranging from 10 seconds to 3 minutes on request. I wrote a blog post on how to do this.
I have been unable to work out how to handle a scenario where the phone is already inside a region when startMonitoringForRegion is called? Other questions have suggested calling requestStateForRegion inside didStartMonitoringForRegion this then calls the method didDetermineState: forRegion:. So the code looks something like this:
- (void)viewDidLoad {
//location manager set up etc...
for (Object *object in allObjects){
CLRegion *region = [self geofenceRegion:object];
[locationManager startMonitoringForRegion:region];
}
}
- (void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region {
[self.locationManager requestStateForRegion:region];
[self.locationManager performSelector:#selector(requestStateForRegion:) withObject:region afterDelay:5];
}
- (void)locationManager:(CLLocationManager *)manager
didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region {
if (state == CLRegionStateInside){
[self locationManager:locationManager didEnterRegion:region];
}
}
Now obviously the method geofenceRegion is my own and it works fine, and the objects contains things like lat long and radius and that all works fine as well so that is not the problem here.
Anyway, the problem with the above code is that it does work if the user is already inside the region when it adds the region to their device (ie. didEnterRegion is done). However the problem is that the method didDetermineState: forRegion: is also called every time one of the boundary regions is crossed as per the apple docs:
The location manager calls this method whenever there is a boundary transition for a region. It calls this method in addition to calling the locationManager:didEnterRegion: and locationManager:didExitRegion: methods. The location manager also calls this method in response to a call to its requestStateForRegion: method, which runs asynchronously.
Now because of this every time a region is entered, didEnterRegion is automatically called but then it is called again because didDetermineState: forRegion: is also automatically called as per the apple docs and this results in didEnterRegion being called again so the region is entered twice when i only want it to be entered once. How can i avoid this?
Thanks for your help.
SOLUTION
The solution really is so simple i was just going about it the wrong way. I had to choose to either use the 2 methods didEnterRegion: and didExitRegion or use didDetermineState: forRegion and create my own methods for entering and exiting the region, both should not be used.
So i have chosen to use only the didDetermineState: forRegion method and my code now looks like this:
Please note that with this method exit region will be called for the region if not inside and if, like me, you only want exit to happen after an enter has happened you will need some sort of method of checking if the region has already been entered (I myself used core data as i was already using this to store other aspects of the regions).
- (void)viewDidLoad {
//location manager set up etc...
for (Object *object in allObjects){
CLRegion *region = [self geofenceRegion:object];
[locationManager startMonitoringForRegion:region];
}
}
- (void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region {
[self.locationManager performSelector:#selector(requestStateForRegion:) withObject:region afterDelay:5];
}
- (void)locationManager:(CLLocationManager *)manager
didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region {
if (state == CLRegionStateInside){
[self enterGeofence:region];
} else if (state == CLRegionStateOutside){
[self exitGeofence:region];
} else if (state == CLRegionStateUnknown){
NSLog(#"Unknown state for geofence: %#", region);
return;
}
}
- (void)enterGeofence:(CLRegion *)geofence {
//whatever is required when entered
}
- (void)exitGeofence:(CLRegion *)geofence {
//whatever is required when exit
}
Just do not use locationManager:didEnterRegion: at all, as locationManager:didDetermineState:forRegion: gives you all the info you need to trigger the on-entry code, which, by the way, should not be the locationManager:didEnterRegion:, use your own selector, which is not a part of CLLocationManagerDelegate protocol.
Another approach is to test for inside a region location when starting to monitor a region. This solution is not that trivial as it sounds though: you need to update current location first by calling startUpdatingLocation, as just reading location property of locationManager will probably give you stale or extremely inaccurate reading.
I have two CLLocationManage within an app. The first one is to monitor the beacon regions , while the other is to monitor the normal CLRegion.
First one in A.m
// Do any additional setup after loading the view.
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
Second one in B.m
gpsLocationManager = [[CLLocationManager alloc] init];
gpsLocationManager.delegate = self;
I am pretty much sure for the latter, I have not called any startMonitoringForRegion on any beacon region. However, it seems the gpsLocationmanager in B keeps receiving the enterRegion callback from the the one in A. So it ends up with my checking the passed-in region para type to make the gpsLocationManager not respond to any callback from beacon region enter.
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
NSLog(#"%d regions under monitor:", self.gpsLocationManager.monitoredRegions.count);
NSLog(#"%d regions in regionArray:", self.regionArray.count);
NSLog(#"Region type %#:", [region class]);
if(![region isKindOfClass:[CLBeaconRegion class]]){
Any idea?
Regards
Hammer
CoreLocation functionality is provided on an app-wide basis.
This is why the common pattern is to initialize your CLLocationManager in your AppDelegate and make it the CLLocationManagerDelegate.
If you need to access your one CLLocationManager in multiple UIViewControllers, I would make it a property of your AppDelegate. You can then forward the callbacks you need to the UIViewControllers if they have been initialized and are visible.
I faced the Same Issue when i created 2 Classes (1 for Geo Fencing(CLRegion or CLCircularRegions) and 2 for Beacon REgions(CLBeaconRegions).Both are singleton Classes with property Called lManager(CLLocationManager).
initially I tried with this Conditional Check
if (manager == self.gpsLocationManager) {
// Do things
}
as Wilmar said. Its not working..
after deep investigation on Region Classes, i Used below condition to variate the regions .
EX :
func locationManager(manager: CLLocationManager!, didStartMonitoringForRegion region: CLCircularRegion!) {
if(region.isMemberOfClass(CLCircularRegion)){
// Do things. here
}
}
Its working for me.
Cheers :p
In your A.m and B.m you can check if the locationManager that is responding is the locationManager you are interested in, and react accordingly. I would however try and stick to a single locationManager as mentioned by davidgyoung.
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
if (manager == self.gpsLocationManager) {
// do things
}
else {
// this is another location manager which I am not interested in
}
}
I have a location app that had the following method in the header of the ViewController.m file for the past couple of days:
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations;
However, I just deleted the above out of my code and the app still runs 100% fine. I don't understand how this is possible when the xcode documentation clearly says that the purpose of this method is to "tell the delegate when new location data is available."
The only thing I can think of is that it says "new location data" and that the above method is already setup in the CoreLocation.h file that I imported, and therefore already available for my use and has already stored the data.
Just want to make sure I understand the theory behind all of this before I move on.
Thank you for the help in clearing this up.
Here is my entire ViewController.m code(with the method still included):
#import "ViewController.h"
#import <CoreLocation/CoreLocation.h>
#interface ViewController () <CLLocationManagerDelegate>
//This tells the delegate that new location data is available. Manager is the object that updates the event, and the locations object is where the array of location data is stored.
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.gpsLM = [[CLLocationManager alloc]init];
NSLog(#"Location services enabled: %u",[CLLocationManager authorizationStatus]);
[self.gpsLM startUpdatingLocation];
self.gpsLM.delegate = self;
CLLocation * currentLocation = self.gpsLM.location;
NSLog(#"Your current location is: %#", currentLocation);
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
-(IBAction)gpsButton{
CLLocation * currentLocation = self.gpsLM.location;
self.gpsLabel.text = [NSString stringWithFormat:#"Your Location is %#", currentLocation];
NSLog(#"Location services enabled: %u",[CLLocationManager authorizationStatus]);
NSLog(#"Your current location is: %#", currentLocation);
}
#end
iOS checks if your class is capable of receiving the data with something like:
if ( [delegate respondsToSelector:#selector(didUpdateLocations:)] ){
[delegate performSelector:#selector(didUpdateLocations:) withArgs:....]
}
So if your delegate doesn't implement the method, then it doesn't attempt to send it.