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
}
}
Related
I have an app that will notify the user every time he approaches to one of my client's stores. There are more than 20 stores, so I have a function that takes the user's location and finds the 20 nearest stores to him and start monitoring the location of these stores, every time the user moves, the app finds the 20 nearest stores again, removes the previous stores from monitoring and start monitoring the new ones.
For some reason, it doesn't work, I'll be happy if one of you (or more :)) will help me to find the problem, Thanks!!
myCode (scroll to see the full code):
Note: the CLLocationManager created on the AppDelegate.m and it's delegate is this class (UIViewController).
-(void)sortClosestStores
{
[self.allStores sortUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
CLLocation *location1=[[CLLocation alloc] initWithLatitude:((Store*)obj1).geoPoint.latitude longitude:((Store*)obj1).geoPoint.longitude];
CLLocation *location2=[[CLLocation alloc] initWithLatitude:((Store*)obj2).geoPoint.latitude longitude:((Store*)obj2).geoPoint.longitude];
float dist1 =[location1 distanceFromLocation:self.locationManager.location];
float dist2 = [location2 distanceFromLocation:self.locationManager.location];
if (dist1 == dist2) {
return NSOrderedSame;
}
else if (dist1 < dist2) {
return NSOrderedAscending;
}
else {
return NSOrderedDescending;
}
}];
if (self.twentyClosestStores==nil) {
self.twentyClosestStores=[NSMutableArray array];
}
if (self.previousTwentyStores==nil) {
self.previousTwentyStores=[NSMutableArray array];
}
self.previousTwentyStores=self.twentyClosestStores;
self.twentyClosestStores=[NSMutableArray array];
for (int i = 0; i < 20; i++) {
[self.twentyClosestStores addObject:[self.allStores objectAtIndex:i]];
}
}
-(void)startMonitoringClosestStores
{
if (![CLLocationManager isMonitoringAvailableForClass:[CLCircularRegion class]]) {
NSLog(#"Monitoring is not available for CLCircularRegion class");
}
for (Store *currentStore in self.twentyClosestStores) {
CLCircularRegion *region=[currentStore createCircularRegion];
[self.locationManager startMonitoringForRegion:region];
}
}
-(void)stopMonitoringStores
{
for (Store *currentStore in self.previousTwentyStores) {
CLCircularRegion *region=[currentStore createCircularRegion];
[self.locationManager stopMonitoringForRegion:region];
}
}
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations
{
if (self.allStores!=nil) {
[self sortClosestStores];
[self stopMonitoringStores];
[self startMonitoringClosestStores];
}
}
-(void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
NSLog(#"Entered"); //Not called even when the user enters one of the regions.
}
Can you please help me? Thanks!
I'm pretty new at CoreLocation myself but I would think that it is not a good idea to call stopMonitoringForRegions and startMonitoringForRegions in didUpdateLocations.
Since you're monitoring regions, the didEnterRegion delegate is what you will be interested in. That will give you the 'hey, I arrived at the X store' event, and in there is where you would probably want to call the code that you currently have in your didUpdateLocations.
You will want to setup CoreLocation probably in your AppDelegate, so you might have something like (sorry about it being Swift, that's what I'm working in right now):
locationManager.delegate = self
// auths:
locationManager.requestAlwaysAuthorization()
locationManager.requestWhenInUseAuthorization()
// config:
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.pausesLocationUpdatesAutomatically = true
locationManager.allowsBackgroundLocationUpdates = true
locationManager.activityType = CLActivityType.AutomotiveNavigation
// start:
locationManager.startMonitoringSignificantLocationChanges()
Then you would have your code:
if (self.allStores!=nil) {
[self sortClosestStores];
[self stopMonitoringStores];
[self startMonitoringClosestStores];
}
Note: I don't think it matters if you call startMonitoringSignificantLocationChanges before or after adding monitored regions, I haven't gotten quite that far in my code yet.
didUpdateLocations is more for when you want to track location e.g. tracking a bicycle ride or jogging session.
Additional explanation:
OK, I think I understand the issue now. There are two aspects to what you want to accomplish:
being notified when the user enters a store's region
dynamically recalculating the nearest N stores as the device moves
My previous answer was geared towards the first issue.
Regarding the second issue, dynamically recalulating nearest N, the code in your didUpdateLocations will not be called unless you tell the location manager to startUpdatingLocation. Just off the top of my head:
// configure the location manager in AppDelegate:
// You will need to experiment with these two properties,
// you probably don't want to use kCLLocationAccuracyBestForNavigation.
// Maybe kCLLocationAccuracyKilometer would be sufficient.
locmgr.distanceFilter = n
locmgr.desiredAccuracy = m
locmgr.pausesLocationUpdatesAutomatically = true
locmgr.startUpdatingLocation()
// this is the delegate that will receive the events due to locmgr.startUpdatingLocation
locationManager:didUpdateLocation {
// Unless you have a specific need for it, I would refactor so that
// you don't need self.PreviousTwentyStores:
[self stopMonitoringStores];
[self sortClosestStores];
[self startMonitoringClosestStores];
}
locationManager:didEnterRegion {
// you are in the region surrounding one of the stores.
}
Alternately, consider just setting a timer and waking the app every N seconds or minutes to recalculate the nearest stores.
As I understand the various aspects of CoreLocation
startUpdatingLocation -> didUpdateLocations -> stopUpdatingLocation
is (in one sense) entirely separate from:
startMonitoringForRegion -> didEnterRegion | didExitRegion -> stopMonitoringForRegion
Additionally, startUpdatingLocation was never called so your didUpdateLocation was never called.
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'm able to get my Current Location using CoreLocation framework. I want to register my current location so that i want to know who has entered and exited the region. For that i have used apple documented method to register a region which is - (void)registerRegionWithCircularOverlay:(MKCircle*)overlay andIdentifier:(NSString*)identifier { } . My question is when does this method gets called?
document link : https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/LocationAwarenessPG/RegionMonitoring/RegionMonitoring.html
Any help?
You must have to take care of permission to access location of user device. So that application is able to track user location.
You can do this by following:
Add a key NSLocationWhenInUseUsageDescription in a .plist file. This is key has a value of type String. You must take care don't use other than String type here.
Ask user to before access to user location, add below line.
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
[self.locationManager requestWhenInUseAuthorization];
Below code will start monitoring region:
// Tell location manager to start monitoring for the region
[self.locationManager startMonitoringForRegion:self.myBeaconRegion];
Also override below methods to make sure that region is discovered:
- (void)locationManager:(CLLocationManager*)manager didEnterRegion:(CLRegion *)region
{
// We entered a region!
NSLog(#"Entered in region");
}
-(void)locationManager:(CLLocationManager*)manager didExitRegion:(CLRegion *)region
{
// Exited the region
NSLog(#"Exited from region");
}
I am using iBeacons, but am running into a small problem.
On first use the user needs to give permission, we then following is called:
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;
[_locationManager requestAlwaysAuthorization];
However, I expected the following delegate method to be called:
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
NSLog(#"Auth status changed: %i", status);
if(status > 3){
// Check if we have to start monitoring beacons
NSLog(#"Do we need to initialise after auth given?");
//[self initialiseLocations];
}
}
I am running iOS 8.0.2, so not sure if this is a bug
This answer might help... it solved it for me at least. Also this website was a good walkthrough for this issue as well.
It looks like you are calling the requestAlwaysAuthorization correctly, but do you call startUpdatingLocation anywhere? Also, double check that in your Info.plist you have the appropriate key and string value added (NSLocationAlwaysUsageDescription since you're using requestAlwaysAuthorization).
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.