In my app(Deployment Target 5.1), i am using CoreLocation to setup a reminder feature, it will basically search the nearby items(have location attached) as device update its current location.
This feature is not working very stable at this stage, sometimes it just doesn't works at all regardless of whether device is in active, suspended and terminated states. i realized that the delegate method locationManager:didUpdateToLocation:fromLocation: didn't get call.
Is there anybody can point me a direction to make this thing work properly?
Here is my setup for the CLLocationManager
sharedLocationManager = [[CLLocationManager alloc]init];
[ESLocationManager defaultManager].delegate = someDelegateClassInstance;
[[ESLocationManager defaultManager] setDesiredAccuracy:kCLLocationAccuracyHundredMeters];
[[ESLocationManager defaultManager] startMonitoringSignificantLocationChanges];
And the implementation of the delegate callback
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
// If it's a relatively recent event, turn off updates to save power
NSDate* eventDate = newLocation.timestamp;
NSTimeInterval howRecent = [eventDate timeIntervalSinceNow];
if (abs(howRecent) < 20.0) {
// If the event is recent,find nearby items.
NSLog(#"latitude %+.6f, longitude %+.6f\n",
newLocation.coordinate.latitude,
newLocation.coordinate.longitude);
//reload nearby list
NSArray *nearbyItems = [self nearbyItemsForLocation:newLocation];
if (nearbyItems && nearbyItems.count ) {
UIApplicationState appState = [[UIApplication sharedApplication] applicationState];
if (appState != UIApplicationStateActive) {
[[UIApplication sharedApplication] presentLocalNotificationNow:[self localNotificationForItems:nearbyItems]];
}
}
}
}
Similar reason to this forum with region monitoring
https://devforums.apple.com/message/251046#251046
Hope this helps
Related
Can I detect whether a user moved to another country?
(Not using Locale.current)
The location detection should be running in background.
I'm hoping to do something like this.
Eg. A user from US leaves the country to UK. Then, when the user reach UK, i am able to detect it at the background and send notification.
You should in your Info.plist, set allowsBackgroundLocationUpdates to YES, this you can search google, and lots of answer for adapt iOS 9.
First you can use CLLocationManager to get the location:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib .
//delegate
self.locationManager.delegate = self;
//The desired location accuracy.
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
//Specifies the minimum update distance in meters.
self.locationManager.distanceFilter = kCLDistanceFilterNone;
self.locationManager.purpose = #"To provide functionality based on user's current location.";
[self.locationManager startUpdatingLocation];
}
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation{
UIAlertView* av = [[UIAlertView alloc] initWithTitle:#"update" message:[NSString stringWithFormat:#"didUpdateToLocation: newLocation: %# old:%#",newLocation,oldLocation] delegate:nil cancelButtonTitle:#"cancel" otherButtonTitles:#"ok", nil nil];
[av show];
}
Secondly, you can use CLGeocoder to get country or city.
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
// get city name
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder reverseGeocodeLocation:newLocation completionHandler:^(NSArray *array, NSError *error)
{
if (array.count > 0)
{
CLPlacemark *placemark = [array objectAtIndex:0];
NSString *city = placemark.locality;
}
else if (error == nil && [array count] == 0)
{
NSLog(#"No results were returned.");
}
else if (error != nil)
{
NSLog(#"An error occurred = %#", error);
}
}];
}
You can give a duration to get location per duration:
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
newLocation = [locations lastObject];
double lat = newLocation.coordinate.latitude;
double lon = newLocation.coordinate.longitude;
NSLog(#"lat:%f,lon:%f",lat,lon);
if (!self.deferringUpdates) {
CLLocationDistance distance = 500;
NSTimeInterval time = 20;
[locationManager allowDeferredLocationUpdatesUntilTraveled:distance
timeout:time];
self.deferringUpdates = YES;
}
}
You can create UNNotificationRequest with exit UNLocationNotificationTrigger.
UNNotificationRequest
A UNNotificationRequest object is used to schedule a local notification and manages the content for a delivered notification. A notification request object contains a UNNotificationContent object with the contents of the notification. It also contains the UNNotificationTrigger object that specifies the conditions that trigger the delivery of the notification. For a delivered notification, you use these objects to fetch information about the notification.
UNLocationNotificationTrigger
A UNLocationNotificationTrigger object causes the delivery of a notification when the device enters or leaves a specified geographic region. Use this object to specify the region information needed to trigger the notification. Location triggers can fire once or they can fire multiple times.
Apps must request access to location services and must have when-in-use permissions to use this class. To request permission to use location services, call the requestWhenInUseAuthorization() method of CLLocationManager before scheduling any location-based triggers.
Flow
Each time user opens app, check his local country and define location trigger
let region: CLRegion = <your code defining country region>
region.notifyOnEntry = false
region.notifyOnExit = true
let trigger = UNLocationNotificationTrigger(region: region, repeats: false)
and using that trigger reschedule notification request (UNNotificationRequest).
When trigger fires (user leaves region) — app will present local notification, and if user taps on it, app starts, and if you add handler on local notification open you can notify your server about user moving away and check his new country and do what you need to do.
I'm Currently monitoring several locations that are backed by core data.
In other words, I have set up a for loop that loops through all of the stored entities in core data and creates a monitored region for all of the entities.
The problem here is that the for loop triggers multiple local notifications when entering one of the regions. The number of notifications almost directly corresponds to the number of monitored regions. So I'm fairly confident this may be whats causing the bug, but I'm not 100 percent sure.
I've noticed that this seems to be a common issue with region monitoring, but I haven't been unable to find an example that incorporates a for loop.
How can I stop multiple notifications being triggered when didEnterRegion gets called?
The method below is called in viewDidLoad. The [DataSource sharedInstance].fetchedResultItems is an array that is populated with the fetchedObjects from a fetched request.
-(void)startMonitoringRegions{
if ([self.locationManager respondsToSelector:#selector(requestWhenInUseAuthorization)]) {
[self.locationManager requestWhenInUseAuthorization];
CLAuthorizationStatus authorizationStatus = [CLLocationManager authorizationStatus];
if (authorizationStatus == kCLAuthorizationStatusAuthorizedAlways ||
authorizationStatus == kCLAuthorizationStatusAuthorizedWhenInUse) {
self.locationManager.distanceFilter = 10;
[self.locationManager startUpdatingLocation];
for (POI *items in [DataSource sharedInstance].fetchResultItems){
NSString *poiName = items.name;
NSNumber *poiLatitude = items.yCoordinate;
NSLog(#"value: %#", poiLatitude);
NSNumber *poiLongitude = items.xCoordinate;
NSLog(#"value: %#", poiLongitude);
NSString *identifier = poiName;
CLLocationDegrees latitude = [poiLatitude floatValue];
CLLocationDegrees longitude = [poiLongitude floatValue];
CLLocationCoordinate2D centerCoordinate = CLLocationCoordinate2DMake(latitude, longitude);
self.regionRadius = 10;
self.region = [[CLCircularRegion alloc] initWithCenter:centerCoordinate radius:400 identifier:identifier];
[self.locationManager startMonitoringForRegion:self.region];
NSLog(#"region: %#", self.region);
NSLog(#"monitored regions %#", self.locationManager.monitoredRegions);
}
}
}
}
Here is the didEnterRegion method
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region{
NSLog(#"entered region!!");
UILocalNotification *localNotification = [[UILocalNotification alloc] init];
if (localNotification) {
localNotification.fireDate = nil;
localNotification.alertBody = [NSString stringWithFormat:#"You are near %#", self.region.identifier];
localNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:10];
localNotification.timeZone = [NSTimeZone defaultTimeZone];
}
[[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
// [[UIApplication sharedApplication]presentLocalNotificationNow:localNotification];
}
Regions are act as a shared resources. When you enter any region a call will be forwarded to all of the location manager. I think somewhere somehow you are creating multiple location manager objects. That is actually causing the multiple calling of didEnterRegion. The number of time didEnterRegion is called depending upon the number of LocationManager you registered. You should write the code in AppDelegate, in this method
(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//Place your code here
}
Just a troubleshooting tip. You can use Obj-C equivalent of the following to see what regions are currently being monitored by the app. Perhaps reviewing the identifiers will shed some light on the problem.
for region in locationManager.monitoredRegions {
debugPrint(region.identifier)
}
And for a clean start you can delete all regions with this:
for region in locationManager.monitoredRegions {
locationManager.stopMonitoringForRegion(region)
}
I identified a strange behaviour on my app using CoreLocation. I'm using the region monitoring functionality but, after authorising the location services (via popup or settings->Location Services) region monitoring fails (The operation couldn’t be completed. kCLErrorDomain error 5.). If I close the app and restart (therefore already authorised) everything works as expected.
My code looks like this:
-(void)initializeLocationServices
{
NSLog(#"Started location services");
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.distanceFilter = kCLDistanceFilterNone;
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
locationManager.pausesLocationUpdatesAutomatically = NO;
[locationManager startUpdatingLocation]; // to show authorisation popup
}
-(CLCircularRegion*)createRegion
{
// Test coordinates
CLLocationDegrees latitude = 50;
CLLocationDegrees longitude = -1;
CLLocationDistance radius = 50; // meters;
// If radius is too large, registration fails automatically, so limit the radius to the maximum value
if (radius > locationManager.maximumRegionMonitoringDistance) {
radius = locationManager.maximumRegionMonitoringDistance;
}
CLCircularRegion* region = [[CLCircularRegion alloc] initWithCenter:CLLocationCoordinate2DMake(latitude, longitude) radius:radius identifier:#"TEST"];
region.notifyOnEntry = YES;
region.notifyOnExit = YES;
NSLog(#"Created region");
return region;
}
-(void)monitorProximity
{
CLRegion *region = [self createRegion];
// Check if support is unavailable
if ( ![CLLocationManager isMonitoringAvailableForClass:[CLRegion class]]) {
NSLog( #"Failed to initialise region monitoring: support unavailable");
return;
}
// Check if authorised
if ([CLLocationManager authorizationStatus] != kCLAuthorizationStatusAuthorized) {
NSLog( #"Failed to initialise region monitoring: app not authorized to use location services");
return;
} else {
NSLog(#"Started monitoring proximity");
}
// Clear out any old regions to prevent buildup.
if ([locationManager.monitoredRegions count] > 0) {
for (id obj in locationManager.monitoredRegions)
[locationManager stopMonitoringForRegion:obj];
}
[locationManager startMonitoringForRegion:region];
}
-(void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region
{
NSLog(#"Started monitoring for region: %#", [region description]);
[locationManager requestStateForRegion:region]; // check if already inside region
}
-(void)locationManager:(CLLocationManager *)manager monitoringDidFailForRegion:(CLRegion *)region withError:(NSError *)error
{
NSLog(#"Failed to start monitoring for region: %#", [error localizedDescription]);
}
-(void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
{
NSLog(#"didDetermineState");
if (state == CLRegionStateInside) {
NSLog(#"inside");
return;
} else if (state == CLRegionStateOutside) {
NSLog(#"outside");
} else {
NSLog(#"unknown");
}
}
-(void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
NSLog(#"didEnterRegion");
}
-(void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
{
NSLog(#"didExitRegion");
}
-(void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
{
NSLog(#"Monitoring authorisation status is now: %#", status == kCLAuthorizationStatusAuthorized ? #"authorized" : #"not authorized");
if (status == kCLAuthorizationStatusAuthorized) {
[self monitorProximity];
}
}
Am I doing something wrong here? Do I have problems with the flow after didChangeAuthorizationStatus gets called?
From other user reports, it seems that kCLErrorDomain 5 is a 'catch all' for region monitoring fails; it doesn't provide much useful information. I believe that your issue is being caused by the line
[locationManager requestStateForRegion:region]; // check if already inside region
which you're calling from inside the delegate method didStartMonitoringForRegion:
I saw something very similar in my own project and taking this line out (or delaying its execution for a while) solved the issue. My best guess is that iOS is still doing running some internal region monitoring code when this delegate method fires, so it's not an appropriate time to call requestStateForRegion:
Try taking this out and see if it is the answer.
kCLErrorDomain code/error 5 means that you have tried to monitor more than 20 CLRegions.
Descriptio here
see startMonitoringForRegion description It says:
An app can register up to 20 regions at a time. In order to report region changes in a timely manner, the region monitoring service requires network connectivity.
kCLErrorDomain 5 is a catch all that can mean many different things.
One of the sources is when you call [locationManager requestStateForRegion:region] which is necessary when you first monitor for a region to know if you're already in the region or not. This is because the didEnter region will only be called when you actually enter the region. Usually this means the first time you monitor for the region, you must wait 5 seconds until the region is not detected, and only then will didEnter region fire off the next time you're in the region.
There are many different reports of causes to the problem:
Ensure no more than 20 beacons are being monitored
5 means "regionMonitoringFailure". Ensure the radius is not too large (not relevant for beacon monitoring).
Ensure location updates are registered
Omit calling requestStateForRegion, however I described above why it's necessary to do so.
Restarting device and bluetooth may help
Try with 30 second delay
None of these worked for me, however. I think my root cause was similar to the iOS 7.1 bug where it just randomly stopped working on some devices. I tried the restart and restart of bluetooth, nothing helped.
Not sure what changed, but I just tried again the next day and it started working.
Basically you may want to try a different device until this one starts working again.
In Xcode 4.5 apple introduced apple new maps. My application heavliy requires map services. And I have noticed in my application it shows the wrong current location until you delete the app and reopen it shows the right current location (Sometimes it doesn't). Just to mention I was connected to 4G when it show the wrong current location. Is there a way to fix this bug because my application heavily needs the right location. Thanks in advance. If you could provide code it would help a lot...Edit: Same Issue in Apple Map App
My Code:
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation {
{
}
if (!oldLocation)
totalDistance = 0.0;
else
totalDistance += [newLocation distanceFromLocation:oldLocation];
}
The old approach from apple docs seems still working in iOS6 (didn't notice this in my active app (it tracks user's route via gps))
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
NSTimeInterval locationAge = -[newLocation.timestamp timeIntervalSinceNow];
if (locationAge > 5.0) return;
if (newLocation.horizontalAccuracy < 0) return;
// proceed with coords here
}
UPDATE from the discussion:
Calculating total and current distance could be done like this (excluding some minor stuff):
// somewhere at the top
CLLocation* lastUsedLocation = nil; // last location used in calculation
CLLocation* pointA = nil; // start of the track
double totalDistance = 0; // total distance of track
double currentDistance = 0; // distance between startTrack point and current location
...
// when you start updating location:
- (void) startTracking {
lastUsedLocation = nil;
pointA = nil;
totalDistance = 0;
currentDistance = 0;
[locationManager startUpdatingLocation];
}
...
// location update callback
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
NSTimeInterval locationAge = -[newLocation.timestamp timeIntervalSinceNow];
if (locationAge > 5.0) return; // filter cached
if (newLocation.horizontalAccuracy < 0) return; // filter invalid
if(!pointA) pointA = [newLocation retain];
if(lastUsedLocation)
{
totalDistance += [newLocation distanceFromLocation:lastUsedLocation];
}
currentDistance = [pointA distanceFromLocation:newLocation];
[lastUsedLocation release];
lastUsedLocation = [newLocation retain];
}
If you need the option to turn off background location on purpose you disable it manually like:
- (void)applicationDidEnterBackground:(UIApplication *)application {
if(backgroundLocationDisabled)
{
[locationManager stopUpdatingLocation];
// additional stuff
}
}
you should check the timestamp .. if i understand your app logic correctly, you could do something like -
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation {
NSDate* time = newLocation.timestamp;
NSTimeInterval timePeriod = [time timeIntervalSinceNow];
if(timePeriod < SOME_MINIMUM_TIME_PERIOD ) { //for eg: timePeriod < 1.0
totalDistance += [newLocation distanceFromLocation:oldLocation];
} else {
// skip.. or do what you do on a 'location-not-updated' event
}
}
I have noticed the same problem with Xcode 4.4+. The problem only occurs (randomly, or so it seems) within Xcode: if you upload the app to the App Store, this is not a problem anymore. In the meantime, please file a bug.
I have a CoreLocation manager in VC, when user pressed "get direction" button, I initalize location manager, and app opens google map direction with current location and some pre defined destination location.
Here is my problem, if app is not in background state, current location nearly always true, bu if app calling from background in same VC and user pressed again "get direction" button , current location generally shows old locations. In short, I'm troubling with multitasking and timestamp of retrieved locations did not solved my problem.
IBAction:
if ( self.locationManager ) {
[_locationManager release];
self.locationManager = nil;
}
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
self.locationManager.distanceFilter = kCLDistanceFilterNone;
self.locationTimer = [NSTimer scheduledTimerWithTimeInterval:LOCATION_TIMER target:self selector:#selector(stopUpdatingLocationTimer) userInfo:nil repeats:NO];
HUD = [MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];
[self.locationManager startUpdatingLocation];
Core Location Delegate:
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
NSTimeInterval locationAge = -[newLocation.timestamp timeIntervalSinceNow];
NSLog(#"%f",locationAge);
if (locationAge > 3.0)
return;
if (newLocation.horizontalAccuracy < 0)
return;
if ( self.currentLocation == nil || self.currentLocation.horizontalAccuracy > newLocation.horizontalAccuracy ) {
self.currentLocation = newLocation;
if (self.currentLocation.horizontalAccuracy <= self.locationManager.desiredAccuracy) {
[self stopUpdatingLocations:YES];
}
}
}
In your example locationAge is a negative representation of the number of seconds since the timestamp of newLocation. This means that locationAge will never be greater than 3 and you're effectively letting every update through the sieve.
Set locationAge like this:
NSTimeInterval locationAge = [newLocation.timestamp timeIntervalSinceNow];
For those who encounter same problem,
Also, some tutorials related with core location on web, lead me this problem.
Of course, I keep CLLocation ivar in my VC, whenever CLLocation ivar
sets and google maps called, my app goes to background.
Then, my app calls from background by user, and start updating locations,
and old CLLocation ivar is not nil and probably best horizantal accuracy then
newly comes. Therefore;
if ( self.currentLocation == nil || self.currentLocation.horizontalAccuracy > newLocation.horizontalAccuracy )
this line cause problems, and old location in CLLocation ivar
never replaced.
So I changed viewDidDisappear like this and I assigned nil value to CLLocation variable and works perfectly.
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[self.locationManager stopUpdatingLocation]; // CLLocationManager
self.locationManager = nil;
[self.locationTimer invalidate]; // NSTimer
self.locationTimer = nil;
self.currentLocation = nil; // CLLocation
}
p.s : thank you Mark Adams