Blip
Blip

Reputation: 1188

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.

Upvotes: 3

Views: 2227

Answers (3)

AndreasZ
AndreasZ

Reputation: 1170

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.

Upvotes: 0

Lyndsey Scott
Lyndsey Scott

Reputation: 37290

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];

}

Upvotes: 1

App Dev Guy
App Dev Guy

Reputation: 5536

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.

Upvotes: 0

Related Questions