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

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.

Related

CLRegionStateUnknown Until Map Refresh

It seems that I'm running into a delegate issue when using regions with CLLocationManager. When I first run the project, I add various regions to the manager and call requestStateForRegion: to determine if I'm currently within any of them. The manager then calls my didDetermineState: function (rather quickly it seems) but all of the passed CLRegionStates are unknown.
When I go into the Maps app and find my current location, then relaunch my application within a few minutes, the states are found correctly.
I've set about 10 regions where one of which is my hard-coded current location, but my current location still shows as unknown. Any ideas?
manager = [[CLLocationManager alloc] init];
manager.delegate = self;
if ([manager respondsToSelector:#selector(requestAlwaysAuthorization)]) {
[manager requestAlwaysAuthorization];
}
for (CLRegion *monitored in [manager monitoredRegions]) {
[manager stopMonitoringForRegion:monitored];
}
NSArray *events = ...;
for (Event *event in events) {
CLLocationCoordinate2D coord = CLLocationCoordinate2DMake(latitude, longitude);
CLRegion *region = [[CLRegion alloc] initCircularRegionWithCenter:coord radius:50.0 identifier:[NSString stringWithFormat:#"%#", event.eventID]];
[manager startMonitoringForRegion:region];
}

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

Wait until there is mobile internet to post data in Objective-C

I am rather new to Objective-C development so please be patient!
I am trying to take data from a user & send that information to an external database. I have managed to work out how to push the data rather easily, but the issue is; due to the nature of my application, there is a high probability that the user will have no mobile connectivity while they're using it. How can I continuously check that the user has mobile connectivity, and then send the data when it's connected? My code for the action is below:
(Just to clarify, the action takes 10 readings of signal over 5 seconds, appends them to an array, calculates the average and the updates the location. In turn, the locationManager sends the data to a cloud service including the average signal reading.
- (IBAction)buttonPressed:(id)sender {
// Make Manager Delegate & Set Accuracy
manager.delegate = self;
manager.desiredAccuracy = kCLLocationAccuracyBest;
// Call Timer
myTimer = [NSTimer scheduledTimerWithTimeInterval:0.5
target:self
selector:#selector(arrayBuild)
userInfo:nil
repeats:YES];
// Initialise Array
resultsArray = [[NSMutableArray alloc]init];
}
#pragma mark CLLocationManagerDelegate Methods
- (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);
if (curentLocation != nil) {
// Code below uses a third party utility to submit data
PFObject *Object = [PFObject objectWithClassName:#"Issue"];
Object[#"Latitude"] = [NSString stringWithFormat:#"%.8f",curentLocation.coordinate.latitude];
Object[#"Longitude"] = [NSString stringWithFormat:#"%.8f",curentLocation.coordinate.longitude];
Object[#"Signal"] = [NSString stringWithFormat:#"%#",_avgNumber];
[Object saveInBackground];
// Update Text Fields
self.latitude.text = [NSString stringWithFormat:#"%.8f",curentLocation.coordinate.latitude];
self.longitude.text = [NSString stringWithFormat:#"%.8f",curentLocation.coordinate.longitude];
self.signal.text = [NSString stringWithFormat:#"%#",_avgNumber];
}
// Stop the Location Update
[manager stopUpdatingLocation];
}
- (void)arrayBuild {
loopCount++;
if (loopCount >= 11) {
// Find Average
NSNumber *avg = [resultsArray valueForKeyPath:#"#avg.self"];
_avgNumber = avg;
// Change Text of Label to Average & Log
self.signal.text = [NSString stringWithFormat:#"%#",_avgNumber];
NSLog(#"%#",_avgNumber);
// Update Location
[manager startUpdatingLocation];
// Invalidate Timer
[myTimer invalidate];
myTimer = nil;
}else{
// Declare Signal Strength
float signalstrength = CTGetSignalStrength();
// Individual Result & Convert to Integer
NSString *result = [NSString stringWithFormat:#"%f", signalstrength];
NSInteger resultInt = [result integerValue];
// Add Object
[resultsArray addObject:[NSNumber numberWithFloat:resultInt]];
// Change Text of Label Each Second
self.signal.text = [NSString stringWithFormat:#"%d",loopCount];
NSLog(#"%f",signalstrength);
}
}
What you need is called "Network Reachability Monitoring". Once you subscribe to it, your will be notified when there are changes to network connectivity state, i.e. device became online or offline, and even type of current connection (WLAN or WWAN).
There is a sample project from Apple and third-party networking libraries (such as AFNetworking) often provide a convenience class for better experience.
EDIT:: Easier solution is to is to use Parse SDK and their saveEventually method instead of saveInBackground. This will, according to documentation, take care of situations when network is not accessible.
There are many question on forum addressing this issue. Apple provides Reachability class for this purpose. You may check This or this question for further clarification.

Objective C location service geocode return values

I am trying to write a method that gets geocoding and returns the place mark as the methods return. I get the location manager portion; it waits to find an location, then runs stop updating. Then I run the geocoding and that is where I have the most questions. First what does that [] block notation mean? I can't seem to return anything out of that ever. So it gets the location in the place mark, but I want to pass that location info back out.
My goal is to have to buttons on a view, one that starts finding the location and writes the starting location to a table, and another button that gets the ending location and writes that to a table. I wanted to reuse the block of code that gets the location.
So in pseudo I want the action code for
button A
placemark objectFoo = call and get my location;
objectFoo write to DB or do whatever
Button B
placemark objectFoo = call and get my location;
objectFoo write to DB or do whatever
Coming from Java I'm confused by what that block of reverseGeocodeLocation:currentLocation is and how I can modify it? I envision an object that goes and gets my location and just hands that back to whatever called it to with it what I please.
- (CLPlacemark *)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
NSLog(#"didUpdateToLocation: %#", newLocation);
CLLocation *currentLocation = newLocation;
if (currentLocation != nil) {
_status.text = [NSString stringWithFormat:#"%.8f", currentLocation.coordinate.longitude];
NSLog(#"longitude: %#", [NSString stringWithFormat:#"%.8f", currentLocation.coordinate.longitude]);
_status.text = [NSString stringWithFormat:#"%.8f", currentLocation.coordinate.latitude];
NSLog(#"latitude: %#", [NSString stringWithFormat:#"%.8f", currentLocation.coordinate.latitude]);
}
// Stop Location Manager
[_locationManager stopUpdatingLocation];
NSLog(#"Resolving the Address");
[geocoder reverseGeocodeLocation:currentLocation completionHandler:^(NSArray *placemarks, NSError *error) {
NSLog(#"Found placemarks: %#, error: %#", placemarks, error);
if (error == nil && [placemarks count] > 0) {
placemark = [placemarks lastObject];
_startLocation.text = [NSString stringWithFormat:#"%# %#\n%# %#\n%#\n%#",
placemark.subThoroughfare, placemark.thoroughfare,
placemark.postalCode, placemark.locality,
placemark.administrativeArea,
placemark.country];
NSLog(#"%#", placemark.subThoroughfare); // address number
NSLog(#"%#", placemark.thoroughfare); // address St
NSLog(#"%#", placemark.postalCode); // zip
NSLog(#"%#", placemark.locality); // city
NSLog(#"%#", placemark.administrativeArea); // state
NSLog(#"%#", placemark.country); // country
NSLog(#" debug county == %#", placemark.country); // country
} else {
NSLog(#"%#", error.debugDescription);
//return placemark;
}
//return placemark;
} ];
NSLog(#" needs to log this to see placemerk country == %#", placemark.country);
return placemark; //placemark object?
}
The [] notation in Objective C can mean a few things, but in this case I think you are referring to the method invocation on an object. So [myObject doSomethingWithInt:i]; is the same as myObject.doSomethingWithInt(i); in Java. Apple has a guide to Objctive c here and there are a number of other introductions and tutorials available on the info pages for the iOS and Objective-C tags here on SO.
In terms of responding to button presses, it is very similar to the event model in Java. You will need to establish a link between the buttons on your storyboard or xib and an event in your UIViewController instance. Again, it is best to work through some examples and tutorials, but essentially you
create a class that subclasses UIViewController
Declare IBAction methods in the .h file for your subclass
In the view in your storyboard or XIB file you set the view's controller class to your subclass
Once you add the buttons to the view you can then drag from the "touches up inside" event on the button to your View Controller and link it to the appropriate IBAction method.
Objective-C and iOS use the delegate pattern heavily. The method in your question is one of the delegate methods of the CLLocationmanagerDelegate protocol. You can declare some #property variables in the .h file for the class that contains this method and change it to something like -
In your .h file
#property (retain,nonatomic) CLPlacemark *currentPlacemark;
in your .m file
- (CLPlacemark *)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
NSLog(#"didUpdateToLocation: %#", newLocation);
CLLocation *currentLocation = newLocation;
if (currentLocation != nil) {
[geocoder reverseGeocodeLocation:currentLocation completionHandler:^(NSArray *placemarks, NSError *error) {
NSLog(#"Found placemarks: %#, error: %#", placemarks, error);
if (error == nil && [placemarks count] > 0) {
self.currentPlacemark = [placemarks lastObject];
}
}
}
In your viewController subclass .h file
#property (strong,nonatomic) *MyLocationMonitor *locationMonitor;
In your ..m file
- (void) viewDidLoad
{
[super viewDidLoad];
self.locationMonitor=[[MyLocationMonitor alloc]init];
}
to expose the current location and then when the button is clicked you could do something like
-(IBAction)button1Clicked:(id)sender {
CLLocation *currentLocation=self.locationMonitor.currentPlacemark;
// TODO - Do something
}

iOS Get User Location as NSString

What I want to accomplish in my app is to get the current user location and display it onscreen in a UILabel. I would like to have an NSString of current user's location with a format similar to this: #"City, State/Country". It would be a one-time operation at the start of the app launch.
I have no prior experience with location in iOS and I would like to get some advice on this one - I'm sure it's quite a simple task.
The process is as follows:
Add CoreLocation.framework to your project. See Linking to a Library or a Framework. If you want to use the address book constants that I use below, you might want to add the AddressBook.framework to your project, too.
Start location services. For this purpose, the "significant change" service (less accurate, but lower power consumption) is probably sufficient for city-level accuracy.
When the location manager informs you of the user's location, then perform a reverse geocode of that location.
Stop location services.
Thus, that might look like:
#import <CoreLocation/CoreLocation.h>
#import <AddressBook/AddressBook.h>
#interface ViewController () <CLLocationManagerDelegate>
#property (nonatomic, strong) CLLocationManager *locationManager;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self startSignificantChangeUpdates];
}
- (void)startSignificantChangeUpdates
{
if ([CLLocationManager locationServicesEnabled])
{
if (!self.locationManager)
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
[self.locationManager startMonitoringSignificantLocationChanges];
}
}
- (void)stopSignificantChangesUpdates
{
[self.locationManager stopUpdatingLocation];
self.locationManager = nil;
}
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
CLLocation *location = [locations lastObject];
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error) {
CLPlacemark *placemark = placemarks[0];
NSDictionary *addressDictionary = [placemark addressDictionary];
NSString *city = addressDictionary[(NSString *)kABPersonAddressCityKey];
NSString *state = addressDictionary[(NSString *)kABPersonAddressStateKey];
NSString *country = placemark.country;
self.label.text = [NSString stringWithFormat:#"%#, %#, %#", city, state, country];
}];
[self stopSignificantChangesUpdates];
}
Note, the location manager's notification of the location is contingent upon the user electing to share that with your app and will happen, even in the best case scenario, asynchronously. Likewise the reverse geocode happens asynchronously.
See Getting User Location from the Location Awareness Programming Guide.
Use -reverseGeocodeLocation:completionHandler: of CLGeocoder.
Try this code snippet, the only trick is that the CLPlacemark (see the Documentation for available info) you get back from the Geocoder has a bunch of info which isn't always consistent, this was one of my tries from an older project, trying to test for location, street name etc... test with your usage case to find a good match:
- (void)getLocationStringForCoordinates:(CLLocationCoordinate2D)coordinates {
if ( CLLocationCoordinate2DIsValid(coordinates) ) {
CLLocation *photoLocation = [[CLLocation alloc] initWithLatitude:coordinates.latitude longitude:coordinates.longitude];
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder reverseGeocodeLocation:photoLocation
completionHandler:^(NSArray *placemarks, NSError *error) {
CLPlacemark *locationPlacemark = [placemarks lastObject];
// Location (popular name, street, area)
NSString *location = locationPlacemark.subLocality ? locationPlacemark.subLocality : (locationPlacemark.name ? locationPlacemark.name : locationPlacemark.thoroughfare);
// sometimes the location can be the same
// as the city name (for small villages), if so
// make sure location is nil to skip it
// else if
// the location name is not being used but is very short 9less then 20 letters, use that instead
if([locationPlacemark.name isEqualToString:locationPlacemark.locality] && [location isEqualToString:locationPlacemark.name])
location = #"";
else if ( ![locationPlacemark.name isEqualToString:location] && locationPlacemark.name.length < 20 )
location = locationPlacemark.name;
// city
NSString *city = locationPlacemark.subAdministrativeArea ? locationPlacemark.subAdministrativeArea : locationPlacemark.locality;
city = city.length > 0 ? [#", " stringByAppendingString:city] : city;
NSString *locationName = [NSString stringWithFormat:#"%#%#", location, city];
}];
}
}
I've found a really nice and simple to follow tutorial on this topic - http://www.appcoda.com/how-to-get-current-location-iphone-user/
Hope it will be helpful to others!
Take a look at the reverseGeocodeLocation:completionHandler: method for CLGeocoder:
http://developer.apple.com/library/ios/#documentation/CoreLocation/Reference/CLGeocoder_class/Reference/Reference.html#//apple_ref/doc/uid/TP40009573
First you will have to use a CLLocationManager to get a CLLocation representing the user's current position.

Resources