iOS Location Services - App is checking for location before allowed - ios

Just trying to determine a user's location. Here's the code:
self.locationManager = [[CLLocationManager alloc] init];
//self.locationManager.delegate = self;
self.locationManager.distanceFilter = 80.0;
[self.locationManager startUpdatingLocation];
self.geoCoder = [[CLGeocoder alloc] init];
[self.geoCoder reverseGeocodeLocation: self.locationManager.location completionHandler:
^(NSArray *placemarks, NSError *error) {
CLPlacemark *placemark = [placemarks objectAtIndex:0];
UIAlertView *locationAlert = [[UIAlertView alloc] initWithTitle:#"Location" message:[NSString stringWithFormat:#"The app has determined that your country is: %# (%#)", placemark.country, placemark.ISOcountryCode] delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
}];
The first time I run the app, I get a double alert popup. The locationAlert pops up first, informing the user of a (null) country, then the default popup that says the app would like to use my location pops up pretty much simultaneously. If I click YES, the next time I run the app I will get the single popup of the user's correct country.
So my question is, how can I first gain permission to access the user's location, THEN run this code to find their location, without getting a null response because I've queried their location without permission?

The reverse geolocation code is trying to use the location property from the locationManager but it is not valid until the CLLocationManager delegate method locationManager:didUpdateToLocation:fromLocation: is called.
You should have a delegate method like this one to handle location updates.

Related

MKMapView or CLLocationManger - to determine user's current location

There are two classes that help regarding map plotting and determining user location - MKMapView and CLLocationManager.
MKMapView has a delegate “didUpdateUserLocation” that tells the user’s current location. At the same time, CLLocationManger has a delegate “didUpdateToLocation” and it also does the same thing.
My question is when to use MKMapView and CLLocationManager. I am able to get current location of the device from MKMapView then why and when should I use CLLocationManager? I tried to get it but I am still not sure.
I think you are confusing the MKMapView property showsUserLocation with CLLocationManager.
As a convenience MKMapView's allow you to simply enable a property to show the users current location on the map UI. This is really handy if you only need to show the user where they are on a map.
However, there are demonstrably many other use cases where simply showing location on a map is not enough and this is where CLLocationManager comes in.
Consider for example a running/training application, where a record of user locations is required to calculate running distance, or even an example from one of my own applications, where by I needed to find the users location (lat/long) to calculate distance to various train stations in real time to identify which was closest for the user. In these examples there is no need for a MapView so using a LocationManager is the right choice.
Anytime you need to programmatically interact with the users location and don't require a map UI basically!
I prefer to use CLLocationManager which is used internally by the MKMapView, so if you don't need to use the map, just use the below code from the location manager.
locationManager = [[CLLocationManager alloc] init];
locationManager.distanceFilter = kCLDistanceFilterNone;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
locationManager.delegate = self;
[locationManager startUpdatingLocation];
Just don't forget to store the locationManager instance somewhere in your class and you can implement the delegate like this.
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
NSLog(#"Error detecting location %#", error);
}
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
CLLocation* location = (CLLocation*)locations.lastObject;
NSLog(#"Longitude: %f, Latitude: %f", location.coordinate.longitude, location.coordinate.latitude);
}
EDIT
You can get the address of the user based on their location using Apple's Geo coder
// Use Apple's Geocoder to figure the name of the place
CLGeocoder* geoCoder = [[CLGeocoder alloc] init];
[geoCoder reverseGeocodeLocation:location completionHandler: ^(NSArray* placemarks, NSError* error) {
if (error != nil) {
NSLog(#"Error in geo coder: %#", error);
}
else {
if (placemarks.count == 0) {
NSLog(#"The address couldn't be found");
}
else {
// Get nearby address
CLPlacemark* placemark = placemarks[0];
// Get the string address and store it
NSString* locatedAt = [[placemark.addressDictionary valueForKey:#"FormattedAddressLines"] componentsJoinedByString:#", "];
location.name = locatedAt;
NSLog(#"The address is: %#", locatedAt);
}
}
}];

How to get current location at the instant UIImagePickerController captures an image?

I have researched on how to get location data from images returned from UIImagePickerController camera. However, I think that the easiest way is to get the current location from CLLocationManager at the instant UIImagePickerController captures an image.
Is there a way of doing this? Is there a way of listening for the "capturePhoto" event, for example?
Just to clarify, the users using my app will likely be moving pretty fast.
Here's what I'd recommend so you don't track the user's location any more than you have to and so you get the user's location closest to the time the image was actually snapped.
Instantiate the CLLocationManager class variable in your viewDidLoad, ex:
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
And make sure it's authorized:
if ([CLLocationManager authorizationStatus] != kCLAuthorizationStatusAuthorizedWhenInUse) {
[self.locationManager requestWhenInUseAuthorization];
}
(Also include the "NSLocationWhenInUseUsageDescription" key in the .plist)
Then you could wait until the UIImagePickerController is actually presented before (1) initializing the dictionary to hold the locations and (2) starting to update the location, ex:
[self presentViewController:self.imagePicker animated:YES completion:nil];
self.locationDictionary = [[NSMutableDictionary alloc] init];
[self.locationManager startUpdatingLocation];
At that point, you can start storing the user's updated locations in an NSMutableDictionary self.locationDictionary class instance variable when CLLocation values are returned from the didUpdateToLocation delegate method, ex:
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
// Format the current date time to match the format of
// the photo's metadata timestamp string
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"YYYY:MM:dd HH:mm:ss"];
NSString *stringFromDate = [formatter stringFromDate:[NSDate date]];
// Add the location as a value in the location NSMutableDictionary
// while using the formatted current datetime as its key
[self.locationDictionary setValue:newLocation forKey:stringFromDate];
}
And then once the image is selected, find its timestamp in the metadata and find the value in the location dictionary with a timestamp key closest to the image timestamp, ex:
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
[self.locationManager stopUpdatingLocation];
// When a photo is selected save it as a UIImage
self.selectedPhoto = info[UIImagePickerControllerOriginalImage];
// Get the timestamp from the metadata and store it as an NSString
self.selectedPhotoDateTime = [[[info valueForKey:UIImagePickerControllerMediaMetadata] objectForKey:#"{Exif}"] objectForKey:#"DateTimeOriginal"];
// If the CLLocationManager is in fact authorized
// and locations have been found...
if (self.locationDictionary.allKeys.count > 0) {
// Sort the location dictionary timestamps in ascending order
NSArray *sortedKeys = [[self.locationDictionary allKeys] sortedArrayUsingSelector: #selector(compare:)];
// As a default, set the selected photo's CLLocation class
// variable to contain the first value in the sorted dictionary
self.selectedPhotoLocation = [self.locationDictionary objectForKey:[sortedKeys objectAtIndex:0]];
// Then go through the location dictionary and set the
// photo location to whatever value in the dictionary
// has a key containing a time most closely before
// the image timestamp. Note that the keys can be compared
// as strings since they're formatted in descending order --
// year to month to day to hour to minute to second.
for (NSString *key in sortedKeys) {
// If the photo's metadata timestamp is less than or equal to
// the current key, set the selected photo's location class
// variable to contain the CLLocation value associated with the key
if ([self.selectedPhotoDateTime compare:key] != NSOrderedAscending) {
self.selectedPhotoLocation = [self.locationDictionary objectForKey:key];
}
// Else if the time in the location dictionary is past
// the photo's timestamp, break from the loop
else {
break;
}
}
}
[self dismissViewControllerAnimated:YES completion:nil];
}
In your .h file you need to add the following to your code:
#interface TakePhotoViewController : UIViewController <CLLocationManagerDelegate>
In your .m file you need the following.
#interface TakePhotoViewController (){
//location stuff
CLLocationManager * manager;
CLGeocoder *geocoder;
CLPlacemark * placemark;
}
The above code set's up relevant references to find your location.
Next add this to your viewDidLoad method:
manager = [[CLLocationManager alloc] init];
geocoder = [[CLGeocoder alloc] init];
This initialises the Location Manager and Geocoder.
Then in your code that either initiates taking a picture or returns the picture to a view use this:
manager.delegate = self;
manager.desiredAccuracy = kCLLocationAccuracyBest;
[manager startUpdatingLocation];
To stop the continuous updating of the location add this to your imagePickerReturn method:
[manager stopUpdatingLocation];
As soon as you stop updating the location you will be saving the very last location that was updated. The location updates once every .5 - 1 second, so even if you are moving or have the camera open for a long time it will only store the location of whatever image you pick. To save the date (which includes time down to milliseconds) use:
NSDate * yourCoreDataDateName = [NSDate date];
For good coding practice to handle any errors you will need this:
//handles the error if location unavailable
- (void) locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error{
NSLog(#"Error: %#", error);
NSLog(#"Failed to get location... :-(");
}
- (void) locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation{
NSLog(#"Location: %#", newLocation);
CLLocation * currentLocation = newLocation;
self.locationTF.text = #"Finding Location...";
if (currentLocation != nil){
[geocoder reverseGeocodeLocation:currentLocation completionHandler:^(NSArray *placemarks, NSError *error) {
if (error == nil && [placemarks count] > 0){
placemark = [placemarks lastObject];
//the code below translates the coordinates into readable text of the location
self.locationLabel.text = [NSString stringWithFormat:#"%# %# , %#, %#, %# %#", placemark.subThoroughfare, placemark.thoroughfare, placemark.locality, placemark.administrativeArea, placemark.country, placemark.postalCode];
}
else{
NSLog(#"%#", error.debugDescription);
self.locationLabel.text = #"Failed to find location";
}
}];
}
}
I must warn you that iOS8 will throw NO error when it can't find a location because it needs you as the programmer to add an alert view to authorise getting locations. See this tutorial on how to overcome this issue:
http://nevan.net/2014/09/core-location-manager-changes-in-ios-8/
I got this tutorial from the most popular asked question about the new error.
The most accurate way to do this would be through the exif metadata. Have a look at this post by Ole Begemann on how to do this.
UPDATE
It seems like Apple doesn't include the location to the metadata to images taken with the Camera from the UIImagePicker.
Another option to get the location in which the image was taken would be to use a custom overlay view for the UIImagePicker and get the location when the takePicture method is called. This would probably achieve the best result.

Check User Coordinates first and then Enable Features

In my project, I need to check user coordinates. If user lives in US, then I will enable a feature which shows air quality. Then further if user lives in Houston and surrounding area, then I will enable another feature which shows another feature, Ozone value. How could I achieve that? I could detect Houston and surrounding area with the following code, but I do not know how to find apart US states from Houston and World.
if((currentLocation.latitude>=28.930661 && currentLocation.latitude<=30.443953 && currentLocation.longitude>=-95.899668 && currentLocation.longitude<=-94.690486) ||
currentLocation.longitude == 0 || currentLocation.latitude == 0) {
// It is in the bounds
[self.opponentAvatarImageView removeFromSuperview];
UIActionSheet *actionsheet = [[UIActionSheet alloc] initWithTitle:#"Would you like to see ozone cloud in your area or a trace of your walking/driving/biking activity" delegate:self cancelButtonTitle:#"Cancel" destructiveButtonTitle:nil otherButtonTitles:#"OzoneMap", #"TraceMap", nil];
[actionsheet showInView:self.view];
}
Use the CLGeocoder class to pass in your location (CLLocation) and have it reverse geocode an address for you. You could even get specific with the City as well.
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder reverseGeocodeLocation:location completionHandler:
^(NSArray* placemarks, NSError* error){
CLPlacemark *placemark = [placemarks firstObject];
if ([placemark.country isEqualToString:#"United States"]) {
// You're in the U.S.
}
}];

IOS get location with CLLocationManager from lat/long

I am trying to Reverse geocode location from Lat/Long value that I get earlier in the App and I would like from this coordinate to find the city name, country name and ISO.
I am currently using CLLocationManager to get actual location information with the folowing code:
//Auto geolocation and find city/country
locationManager.delegate=self;
//Get user location
[locationManager startUpdatingLocation];
[self.geoCoder reverseGeocodeLocation: locationManager.location completionHandler:
^(NSArray *placemarks, NSError *error) {
//Get nearby address
CLPlacemark *placemark = [placemarks objectAtIndex:0];
//String to hold address
locatedAtcountry = placemark.country;
locatedAtcity = placemark.locality;
locatedAtisocountry = placemark.ISOcountryCode;
//Print the location to console
NSLog(#"Estas en %#",locatedAtcountry);
NSLog(#"Estas en %#",locatedAtcity);
NSLog(#"Estas en %#",locatedAtisocountry);
[cityLabel setText:[NSString stringWithFormat:#"%#,",locatedAtcity]];
[locationLabel setText:[NSString stringWithFormat:#"%#",locatedAtcountry]];
//Set the label text to current location
//[locationLabel setText:locatedAt];
}];
It is working perfectly but, It is possible to do the same from Long/Lat value that I had already saved in the device and not with the current location like on the actual code ?
Update and solution:
Thanks Mark for the answer, I finally use the following code to get info from saved coordinate:
CLLocation *location = [[CLLocation alloc] initWithLatitude:37.78583400 longitude:-122.40641700];
[self.geoCoder reverseGeocodeLocation: location completionHandler:
^(NSArray *placemarks, NSError *error) {
//Get nearby address
CLPlacemark *placemark = [placemarks objectAtIndex:0];
//String to hold address
locatedAtcountry = placemark.country;
locatedAtcity = placemark.locality;
locatedAtisocountry = placemark.ISOcountryCode;
//Print the location to console
NSLog(#"Estas en %#",locatedAtcountry);
NSLog(#"Estas en %#",locatedAtcity);
NSLog(#"Estas en %#",locatedAtisocountry);
[cityLabel setText:[NSString stringWithFormat:#"%#",locatedAtcity]];
[locationLabel setText:[NSString stringWithFormat:#"%#",locatedAtcountry]];
//Set the label text to current location
//[locationLabel setText:locatedAt];
}];
Yes. Create a CLLocation object using the initWithLatitude:longitude: method using your saved lat/lon values, and pass that to reverseGeocodeLocation:.
I am surprised that you say this is working (although, if you're on the simulator, location services are simulated anyway, which might be the reason) because when you call startUpdatingLocation, your implementation of CLLocationManagerDelegate methods like locationManager:didUpdateToLocation:fromLocation: get called. (You've implemented these right?) It is only when this (and other) delegate method is called that you can be certain that you have successfully determined the user's location.
You may want to read up on the CLLocationManagerDelegate protocol and on Location Services best practices as documented by Apple.

CLGeocoder reverseGeocodeLocation returns 'kCLErrorDomain error 9'

I'm developing an iOS app with reverse geocoding features according to this article: geocoding tutorial
But when I test like this on simulator, I get 'kCLErrorDomain error 9'. I've searched a lot and there are only error 0 or 1 not 9.
Here is my code in viewDidLoad:
self.locationManager = [[CLLocationManager alloc]init];
self.locationManager.delegate = self;
self.locationManager.distanceFilter = 80.0;
[self.locationManager startUpdatingLocation];
CLGeocoder *geocoder = [[[CLGeocoder alloc] init] autorelease];
[geocoder reverseGeocodeLocation:self.locationManager.location
completionHandler:^(NSArray *placemarks, NSError *error) {
NSLog(#"reverseGeocodeLocation:completionHandler: Completion Handler called!");
if (error){
NSLog(#"Geocode failed with error: %#", error);
return;
}
if(placemarks && placemarks.count > 0)
{
//do something
CLPlacemark *topResult = [placemarks objectAtIndex:0];
NSString *addressTxt = [NSString stringWithFormat:#"%# %#,%# %#",
[topResult subThoroughfare],[topResult thoroughfare],
[topResult locality], [topResult administrativeArea]];
NSLog(#"%#",addressTxt);
}
}];
Thank you very much.
The Core Location error codes are documented here.
Code values 8, 9, and 10 are:
kCLErrorGeocodeFoundNoResult,
kCLErrorGeocodeFoundPartialResult,
kCLErrorGeocodeCanceled
Based on the code shown, the most likely reason you'd get error 8 is that it's trying to use location immediately after calling startUpdatingLocation at which time it might still be nil.
It usually takes a few seconds to obtain the current location and it will most likely be nil until then (resulting in geocode error 8 or kCLErrorGeocodeFoundNoResult). I'm not sure what error code 9 means by "FoundPartialResult" but Apple's GeocoderDemo sample app treats both the same way (as "No Result").
Try moving the geocoding code (all the code after the startUpdatingLocation call) to the delegate method locationManager:didUpdateToLocation:fromLocation:. The location manager will call that delegate method when it actually has a location and only then is it safe to use location.
There, after the geocoding is successful (or not), you may want to call stopUpdatingLocation otherwise it will try geocoding every time the user location is updated.
You may also want to check the accuracy (newLocation.horizontalAccuracy) and age (newLocation.timestamp) of the received location before trying to geocode it.
It turns out I mixed up the longitute and latitude when creating the location. Thought I'd add this as something for people to check.

Resources