in my IOS app I am implementing geofencing. In the current implementation I am using code like this:
CLRegion* region3 = [[CLRegion alloc] initCircularRegionWithCenter:coordinates radius:100 identifier:#"region3"];
[self.locationManager startMonitoringForRegion:region desiredAccuracy:kCLLocationAccuracyHundredMeters];
and then I am using these delegate methods:
(void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region{
NSLog(#"didenterregion");
}
(void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region{
NSLog(#"didexitregion");
}
(void)locationManager:(CLLocationManager *)manager monitoringDidFailForRegion:(CLRegion *)region withError:(NSError *)error
{NSLog(#"monitoringDidFailForRegion");}
However, this code works fine only for radius bigger than 100m.
Here are some questions:
Apple says that in ios6 and above a radius between 1 and 400m is supported for devices 4s and above. Since I do not care how long it will take for the message to be viewed (like I do not care to see the message when entering the region but I do care to see at a latter if I have passed from that region once) can I use smaller radius? I am interested in something like 50m radius or smaller? (in some regions even 20m will be needed for my case).
I have also think that. Apple says that up to 20 regions could be supported. What are the advantages/ disadvantages of a sollution like this (I haven't implemented it yet but I want your opinion).
The pseudocode would be like this:
Declare the regions - save them in an array
Do not call start monitoring
And then in the delegate method:
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation
{
for loop in all my regions {
if ([region containsCoordinate: newLocation.coordinate])
code for entering region
}
}
Would it be slower?
Would it consume more battery? (I think monitoring for regions is not battery consuming)?
Could it be more accurate?
Can I have more than 20 regions since I am not registering for monitor?
Thanks in advance.
1.
I suspect the second (didUpdateToLocation:-based) implementation would be more expensive (in terms of battery life) versus the first implementation, simply because you would only run the code in the first (startMonitoringForRegion:-based) implementation if and only if the device came within the radius of one of the (maximum of 20) regions you're tracking.
Whereas in the second implementation, code has to run each time there's a "didUpdateToLocation:" delegate call (which will happen fairly often) and then the code inside the delegate method will get run.
B.T.W., you say the code works fine for a radius of 100m of above, but then the Apple documentation says it should work in iOS6 with "a radius between 1 and 400m is supported for devices 4s and above."
Is your "100m" number your practical result or is it a limitation of the device you're using (something older than an iPhone 4s or an older iOS version)?
2.
Doing anything in the background does consume battery but Apple has optimized CoreLocation for this somewhat (provided you set the correct flag in your app's info.plist file)
3.
I think both will be about equally accurate, except for the fact it may take up to a few minutes for "startMonitoringForRegion:" to report that the region was entered or exited.
4.
And yeah, in the second implementation, you can have as many regions as you want to track. However, the more code you run in the background, the hotter the battery gets and the more likely it is you're draining the battery faster.
Related
I'm using google place API to get proper information of current place.
I used this code in the App and get success to fetch list of all nearby locations, but how can I recognize my exact current location from the list?
I read doc and find that we get current location from the list based on likelihoods (value between 0 to 1).
A higher value of likelihoods means a greater probability that the place is the best match.
But my current location is coming into this list but likelihoods value is not higher.
So I have two questions:
1) How can I recognize my exact current location? Any other setting is there or any alert-net way is there? (Without use of place picker)
2) If I leave current place then I want to perform some action so How can I get that I left this place recently? like checked-In/Out?
Google's API requires that you initialize a CLLocationManager. Since you already have a location manager, you can set your class to be a CLLocationManagerDelegate. This provides you with native iOS methods that mean you can get both an exact location and a notification that the location has changed
To get an exact location:
[self.locationManager requestLocation]; //Get the current location once (iOS 9 and above)
To monitor location:
[self.locationManager startUpdatingLocation] // Ask for the current location until you call stopUpdatingLocation
// or
[self.locationManager startMonitoringSignificantLocationChanges] // Tells you when your location has greatly changed
You then need to conform to the protocol, which has 2 methods at minimum:
- (void)locationManager:(CLLocationManager *)manager
didUpdateLocations:(NSArray<CLLocation *> *)locations
// Do anything that you need to do once you have a location update
- (void)locationManager:(CLLocationManager *)manager
didFailWithError:(NSError *)error
// Your location update failed, handle it
I'm using a CLLocationManager to get a users location (primarily running in the background). I create it like so and set the accuracy to best lowest as to have minimal impact on battery.
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers;
I then implement the delegate method:
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation
I'm running the application in the simulator and simulate a 'freeway drive'. The location manager delegate method seems to be updating every second which I'm thinking isn't correct as the 'car' is probably not travelling at 3km/sec (in fact it's travelling at 50km/hr).
Is my understanding of this functionality correct or am I missing something? Will this have a large negative impact on battery life? Would this change in real life usage depending on cell tower/GPS/wifi signals?
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.