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.
Related
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 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
}
}
It's a further problem based on the question in iBeacon Bluetooth didEnterRegion and didExitRegion methods are never fired which have been solved.
In detail, the method of didEnterRegion and didExitRegion are never fired while the beacon application in the background or the device locked. In addition, these two methods could be triggered normally while the beacon application in the front.
My application is based on the apple demo "Airlocated (sample code, provided by apple inc)" in the bellow link:
https://developer.apple.com/library/ios/samplecode/AirLocate/Introduction/Intro.html#//apple_ref/doc/uid/DTS40013430-Intro-DontLinkElementID_2. I hardly modify any code except adding some code as bellow:
in file "APLAppDelegate.m"
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
NSLog(#"Entered region: %#", region);
[self sendLocalNotificationForBeaconRegion:(CLBeaconRegion *)region];
}
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
{
NSLog(#"Exited region: %#", region);
}
in file APLMonitoringViewcontroller.m
- (void)updateMonitoredRegion
{
...
[self.locationManager startMonitoringForRegion:region];
[self.locationManager startRangeingForRegion:region];
...
}
Complement a bit. I have tried the bellow methods:
Set "Location updates" Background Mode to YES
specify notifyOnExit and notifyOnEntry as true
reboot iPhone4s with iOS 7.1.2
Could anyone give me some suggestions on it?
How long are you waiting for background detections? Under some circumstances, this can take up to 15 minutes. See here for details.
As described in that article, you do not need to set location updates Background Mode to YES, nor do you need to specify notifyOnExit and notifyOnEntry as true.
I'm investigating the use of region monitoring for my app. Basically, I want to define a circle area and if the user is outside this circle, then the app won't work.
As I understand it, region monitoring only checks to see if the user crosses the boundary.
If this is the case, can somebody point me in the direction of a tutorial/blogpost which can help me achieve my goal?
Region Monitoring active on both if user comes in the boundary and goes out from boundary
Following methods are useful to check user comes in or goes out from particular defined boundary area :
-(void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {
NSLog(#"User Enters in Region");
}
-(void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region {
NSLog(#"User Goes out from Region");
}
calculate the distance from current location to center of circle.
If distance > radius then outside.
You can use regions for monitoring user's in/out activity but normally you could register for Significant Location Change and then check whether user's location is in some limited distance from the point. You can easily calculate distance between two CLLocation points by calling on one sth like this:
[myLoc distanceFromLocation:locationOfCenterOfCircle]
and compare it with radius. Method above returns result in meters. In this case worth reading will be this especially about Significant Location Change.
You can perform a requestStateForRegion:(CLRegion *) on the CLLocationManager.
This way the delegate class' delegate method :
-(void) locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region will be fired.
In there you can check if you're inside the region or outside.
So basically if you request the state somewhere near the beginning of your app, you can determine whether the user is in or outside your fence.
That'd make something like :
-(void) locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region{
if (state == CLRegionStateInside){
// Inside geofence
} else {
// Keep state disabled
}
Hi I am implementing Location services in my app. First I have to know my Coordinates to get the distance between some places that I have in a list and the device. Then if I go into a place I can make a check in, so, I need to get coordinates again, and the problem is here. Second time I try to get coordinates, the method -(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations is not called.. and I can not get new Coordinates.
My manager is located in a NSObject sublcass with this code:
(id)init {
if ( self = [super init] ) {
if ([CLLocationManager locationServicesEnabled])
{
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
[locationManager startUpdatingLocation];
}
}
return self;
}
-(void) checkLongLatitudeAgain {
[locationManager startUpdatingLocation];
}
#pragma mark Delegates de CLLocationManager
//
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
NSLog(#"LON%f", manager.location.coordinate.longitude);
NSLog(#"LAT:%f", manager.location.coordinate.latitude);
NSTimeInterval howRecentNewLocation = [newLocationeventDate timeIntervalSinceNow];
if (manager.location.horizontalAccuracy <= 100.0 && howRecentNewLocation < -0.0 && howRecentNewLocation > -20.0){
//Usar coordenada
[self.delegate getLocationForCheckIn:manager.location];
[self stopUpdatingLocation:#"Fins"];
}
}
// ---------------------------------------------------------------------------------------------------------------------
-(void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error{
//
if ([error code] != kCLErrorLocationUnknown) {
[self stopUpdatingLocation:NSLocalizedString(#"Error", #"Error")];
}
//
}
// ---------------------------------------------------------------------------------------------------------------------
- (void)stopUpdatingLocation:(NSString *)state {
//Detenemos la lectura del GPS
[locationManager stopUpdatingLocation];
locationManager.delegate = nil;
NSLog(#"Stop gps");
//
}
I call the class when the list of places is open, and also when inside a place the user press checkIn button. Both times I do it with this code:
WPLocationManager *location = [[WPLocationManager alloc]init];
[location checkLongLatitudeAgain];
You are creating a new manager every time:
WPLocationManager *location = [[WPLocationManager alloc]init];
[location checkLongLatitudeAgain];
That new manager is not assigned to any delegate.
You need to use the previous manager you have created and assigned to your delegate, something like:
[locationManager checkLongLatitudeAgain];
You can check the documentation at http://developer.apple.com - https://developer.apple.com/library/ios/documentation/userexperience/conceptual/LocationAwarenessPG/CoreLocation/CoreLocation.html
In particular you can check the Starting the Standard Location Service and Starting the Significant-Change Location Service sections. You have to use the startMonitoringSignificantLocationChanges or startUpdatingLocation method of CLLocationManager, cache your location somewhere and update it only when a new location is received, otherwise like it is stated in the documentation: "If a location update has already been delivered, you can also get the most recent location data directly from the CLLocationManager object without waiting for a new event to be delivered".
i dont know why you are initiating your location manager again again, also even if you some how manage to solve current problem but it's not proper way of dealing with location manage based applications.I had been in trouble previously when i was working on location based app. the best approach for location based app is singleton.
apple forum discussion
you can find
this
and this very helpful.
just an advice, :)
Thanks.
In iOS8 for me I had to call [locationManager stopUpdatingLocation]; before calling [locationManager startUpdatingLocation] to start getting updates second time and it works for me.