iOS: Can I detect the user located country? - ios

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.

Related

Multiple Local Notifications glitch from didEnterRegion:

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)
}

Cllocationmanager Didupdatelocation delegate not getting called without internet

I am currently working on a location based ios application. I am using didupdatelocation delegate method for showing user's current location. It works fine when I connect my device to internet. But when I disconnect the internet, it behaves strange and didupdatelocation is not getting called further. Please give a solution.
EDITED with code details
- (void)viewDidLoad
{
[super viewDidLoad];
locationManager = [[CLLocationManager alloc]init]; // initializing locationManager
locationManager.delegate = self; // we set the delegate of locationManager to self.
locationManager.desiredAccuracy = kCLLocationAccuracyBest; // setting the accuracy
[locationManager startUpdatingLocation]; //requesting location updates
}
-(void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error{
UIAlertView *errorAlert = [[UIAlertView alloc]initWithTitle:#"Error" message:#"There was an error retrieving your location" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles: nil];
[errorAlert show];
NSLog(#"Error: %#",error.description);
}
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
CLLocation *crnLoc = [locations lastObject];
latitude.text = [NSString stringWithFormat:#"%.8f",crnLoc.coordinate.latitude];
longitude.text = [NSString stringWithFormat:#"%.8f",crnLoc.coordinate.longitude];
altitude.text = [NSString stringWithFormat:#"%.0f m",crnLoc.altitude];
speed.text = [NSString stringWithFormat:#"%.1f m/s", crnLoc.speed];
}
#ANSHAD per your comment, if you are using an older iPad without GPS, the only way you can get location data is with WiFi so if you turn off WiFi, you will not get location updates. If you want GPS without WiFi you can buy an external GPS module see here
You can use the base GPS of the iPhone which works without the internet too. To access it you need to use the CoreLocation framework.
You can refer to this : CoreLocation methods to use the GPS data.
Let me know if any other help required.

Core Location can't get data in viewDidLoad

I wrote a simple iOS application that retrieves location information and then uses the location to request Yahoo Weather.
The problem is that even when I call the Core Location in the viewDidLoad, it won't give me the result immediately.
So why can't I get the location information?
How can I get the location information in viewDidLoad?
The pseudocode currently is something like:
- (void)viewDidLoad
{
[super viewDidLoad];
self.locManager = [[CLLocationManager alloc] init];
self.locManager.delegate = self;
self.locManager.desiredAccuracy = kCLLocationAccuracyBest;
self.locManager.distanceFilter = 100;
[self.locManager startUpdatingLocation];
//won't get the current location right now, so the output will be null
NSLog(#"Current Location Longitude: %#", self.longitudeString);
NSLog(#"Current Location Latitude: %#", self.latitudeString);
}
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
CLLocation *newLocation = [locations lastObject];
self.longitudeString = [NSString stringWithFormat:#"%.8f", currentLocation.coordinate.longitude];
self.latitudeString = [NSString stringWithFormat:#"%.8f", currentLocation.coordinate.latitude];
}
Location updates are not provided as instantly as you are expecting, you need to wait few seconds (2-3 or may be more) to get precise location update. If you want to have location data in viewDidLoad then you should init your location manager (and call startUpdatingLocation) before invoking the ViewController (since then it is not guaranteed that you will have location-data in viewDidLoad).

iOS 7 CoreLocation: region monitoring fails on the first time after location services are authorised

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.

startMonitoringForRegion is not sending to didEnterRegion or didExitRegion

I have a location based app, all though the region is correct the app never moves to didEnterRegion or didExitRegion
for (int x = 0; x <= [[[TaskStore sharedStore] allTasks]count]-1; x++)
{
NSArray *tasks = [[TaskStore sharedStore] allTasks];
Task *selectedTask = [tasks objectAtIndex:x];
location.latitude = selectedTask.locationCoord.coordinate.latitude;
location.longitude = selectedTask.locationCoord.coordinate.longitude;
NSString* desiriedLoc = [selectedTask locationName];
CLRegion *region = [[CLRegion alloc] initCircularRegionWithCenter: location radius: 30.0 identifier: desiriedLoc];
NSLog(#"Entered new Location in Region %#", region);
[locManager startMonitoringForRegion:region];
}
-(void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
NSLog(#"didEnterRegion for %#",region.identifier);
UIAlertView *alr=[[UIAlertView alloc] initWithTitle:#"Reminder didEnterRegion"
message:region.identifier delegate:nil cancelButtonTitle:nil otherButtonTitles:#"Ok",nil];
[alr show];
}
-(void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
{
NSLog(#"didExitRegion for %#",region.identifier);
UIAlertView *alr=[[UIAlertView alloc] initWithTitle:#"Reminder didExitRegion" message:region.identifier delegate:nil cancelButtonTitle:nil otherButtonTitles:#"Ok",nil];
[alr show];
}
Here is a string print out from my location:
Entered new Location with the coordinates Latitude: 51.509980 Longitude: -0.133700
and here is a string print out from the region:
Entered new Location in Region (identifier London) <+51.50998000,-0.13370000> radius 30.00m
As #verbumdei commented, the only way you will get the -didEnterRegion and -didExitRegion is to establish a CLLocationManagerDelegate. Set your view as the delegate and add those methods and you should see the update.
One thing to note, you are using a 30M radius, you will need to be quite approximate in your location if you want to trigger updates. This is fairly easy to do in the Simulator, but in real life usage (on device), 30M accuracy is a bit tougher. I would start with 100M and work your way down based on experience.
If you start monitoring while staying in the target region, nothing triggered.
Because it's not really an "didEnterRegion" event.
You don't seem to be setting
region.notifyOnEntry = YES;
region.notifyOnExit = YES;
Without explicitly setting these properties, the specified events will not get fired.

Resources