UIImagePickerController and extracting EXIF data from existing photos - iphone
It's well known that UIImagePickerController doesn't return the metadata of the photo after selection. However, a couple of apps in the app store (Mobile Fotos, PixelPipe) seem to be able to read the original files and the EXIF data stored within them, enabling the app to extract the geodata from the selected photo.
They seem to do this by reading the original file from the /private/var/mobile/Media/DCIM/100APPLE/ folder and running it through an EXIF library.
However, I can't work out a way of matching a photo returned from the UIImagePickerController to a file on disk. I've explored file sizes, but the original file is a JPEG, whilst the returned image is a raw UIImage, making it impossible to know the file size of the image that was selected.
I'm considering making a table of hashes and matching against the first x pixels of each image. This seems a bit over the top though, and probably quite slow.
Any suggestions?
Have you took a look at this exif iPhone library?
http://code.google.com/p/iphone-exif/
Gonna try it on my side. I'd like to get the GPS (geotags) coordinates from the picture that has been taken with the UIImagePickerController :/
After a deeper look, this library seems to take NSData info as an input and the UIImagePickerController returns a UIImage after taking a snapshot. In theory, if we use the selected from the UIkit category for UIImage
NSData * UIImageJPEGRepresentation (
UIImage *image,
CGFloat compressionQuality
);
Then we can convert the UIImage into a NSData instance and then use it with the iPhone exif library.
UPDATE:
I gave a test to the library mentioned above and it seems to work. However because of my limited knwoledge about the EXIF format and the lack of high level API in the library, I don't manage to get the values for the EXIF tags.
Here's my code in case any of you can go further :
#import "EXFJpeg.h"
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)image editingInfo:(NSDictionary *)editingInfo {
NSLog(#"image picked %# with info %#", image, editingInfo);
NSData* jpegData = UIImageJPEGRepresentation (image,0.5);
EXFJpeg* jpegScanner = [[EXFJpeg alloc] init];
[jpegScanner scanImageData: jpegData];
EXFMetaData* exifData = jpegScanner.exifMetaData;
EXFJFIF* jfif = jpegScanner.jfif;
EXFTag* tagDefinition = [exifData tagDefinition: [NSNumber numberWithInt:EXIF_DateTime]];
//EXFTag* latitudeDef = [exifData tagDefinition: [NSNumber numberWithInt:EXIF_GPSLatitude]];
//EXFTag* longitudeDef = [exifData tagDefinition: [NSNumber numberWithInt:EXIF_GPSLongitude]];
id latitudeValue = [exifData tagValue:[NSNumber numberWithInt:EXIF_GPSLatitude]];
id longitudeValue = [exifData tagValue:[NSNumber numberWithInt:EXIF_GPSLongitude]];
id datetime = [exifData tagValue:[NSNumber numberWithInt:EXIF_DateTime]];
id t = [exifData tagValue:[NSNumber numberWithInt:EXIF_Model]];
....
....
The retrieving of tags definition is OK, but all tag values returns nil :(
In case you want to give a try to the library, you need to define a global variable to get it running (as explained in the doc but hum.. :/)
BOOL gLogging = FALSE;
UPDATE 2
Answer here : iPhone - access location information from a photo
A UIImage does not encapsulate the meta information, so we're stuck : for sure, no EXIF info will be given through this interface.
FINAL UPDATE
Ok I managed to get it working, at least to geotag properly pictures returned by the picker.
Before triggering the UIImagePickerController, it's up to you to use the CLLocationManager to retrieve the current CLocation
Once you have it, you can use this method that uses exif-iPhone library to geotag the UIImage from the CLLocation :
-(NSData*) geotagImage:(UIImage*)image withLocation:(CLLocation*)imageLocation {
NSData* jpegData = UIImageJPEGRepresentation(image, 0.8);
EXFJpeg* jpegScanner = [[EXFJpeg alloc] init];
[jpegScanner scanImageData: jpegData];
EXFMetaData* exifMetaData = jpegScanner.exifMetaData;
// end of helper methods
// adding GPS data to the Exif object
NSMutableArray* locArray = [self createLocArray:imageLocation.coordinate.latitude];
EXFGPSLoc* gpsLoc = [[EXFGPSLoc alloc] init];
[self populateGPS: gpsLoc :locArray];
[exifMetaData addTagValue:gpsLoc forKey:[NSNumber numberWithInt:EXIF_GPSLatitude] ];
[gpsLoc release];
[locArray release];
locArray = [self createLocArray:imageLocation.coordinate.longitude];
gpsLoc = [[EXFGPSLoc alloc] init];
[self populateGPS: gpsLoc :locArray];
[exifMetaData addTagValue:gpsLoc forKey:[NSNumber numberWithInt:EXIF_GPSLongitude] ];
[gpsLoc release];
[locArray release];
NSString* ref;
if (imageLocation.coordinate.latitude <0.0)
ref = #"S";
else
ref =#"N";
[exifMetaData addTagValue: ref forKey:[NSNumber numberWithInt:EXIF_GPSLatitudeRef] ];
if (imageLocation.coordinate.longitude <0.0)
ref = #"W";
else
ref =#"E";
[exifMetaData addTagValue: ref forKey:[NSNumber numberWithInt:EXIF_GPSLongitudeRef] ];
NSMutableData* taggedJpegData = [[NSMutableData alloc] init];
[jpegScanner populateImageData:taggedJpegData];
[jpegScanner release];
return [taggedJpegData autorelease];
}
// Helper methods for location conversion
-(NSMutableArray*) createLocArray:(double) val{
val = fabs(val);
NSMutableArray* array = [[NSMutableArray alloc] init];
double deg = (int)val;
[array addObject:[NSNumber numberWithDouble:deg]];
val = val - deg;
val = val*60;
double minutes = (int) val;
[array addObject:[NSNumber numberWithDouble:minutes]];
val = val - minutes;
val = val*60;
double seconds = val;
[array addObject:[NSNumber numberWithDouble:seconds]];
return array;
}
-(void) populateGPS:(EXFGPSLoc* ) gpsLoc :(NSArray*) locArray{
long numDenumArray[2];
long* arrPtr = numDenumArray;
[EXFUtils convertRationalToFraction:&arrPtr :[locArray objectAtIndex:0]];
EXFraction* fract = [[EXFraction alloc] initWith:numDenumArray[0]:numDenumArray[1]];
gpsLoc.degrees = fract;
[fract release];
[EXFUtils convertRationalToFraction:&arrPtr :[locArray objectAtIndex:1]];
fract = [[EXFraction alloc] initWith:numDenumArray[0] :numDenumArray[1]];
gpsLoc.minutes = fract;
[fract release];
[EXFUtils convertRationalToFraction:&arrPtr :[locArray objectAtIndex:2]];
fract = [[EXFraction alloc] initWith:numDenumArray[0] :numDenumArray[1]];
gpsLoc.seconds = fract;
[fract release];
}
This works with iOS5 (beta 4) and the camera roll (you need type defs for the blocks in the .h):
-(void) imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary *)info
{
NSString *mediaType = [info objectForKey:UIImagePickerControllerMediaType];
if ([mediaType isEqualToString:(NSString*)kUTTypeImage]) {
NSURL *url = [info objectForKey:UIImagePickerControllerReferenceURL];
if (url) {
ALAssetsLibraryAssetForURLResultBlock resultblock = ^(ALAsset *myasset) {
CLLocation *location = [myasset valueForProperty:ALAssetPropertyLocation];
// location contains lat/long, timestamp, etc
// extracting the image is more tricky and 5.x beta ALAssetRepresentation has bugs!
};
ALAssetsLibraryAccessFailureBlock failureblock = ^(NSError *myerror) {
NSLog(#"cant get image - %#", [myerror localizedDescription]);
};
ALAssetsLibrary *assetsLib = [[ALAssetsLibrary alloc] init];
[assetsLib assetForURL:url resultBlock:resultblock failureBlock:failureblock];
}
}
There is a way in iOS 8
Without using any 3rd party EXIF library.
#import <Photos/Photos.h>
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
NSURL *url = [info objectForKey:UIImagePickerControllerReferenceURL];
PHFetchResult *fetchResult = [PHAsset fetchAssetsWithALAssetURLs:#[url] options:nil];
PHAsset *asset = fetchResult.firstObject;
//All you need is
//asset.location.coordinate.latitude
//asset.location.coordinate.longitude
//Other useful properties of PHAsset
//asset.favorite
//asset.modificationDate
//asset.creationDate
}
Apple has added an Image I/O Framework in iOS4 which can be used to read EXIF data from pictures. I don't know if the UIImagePickerController returns a picture with the EXIF data embedded though.
Edit: In iOS4 you can fetch the EXIF data by grabbing the value of the UIImagePickerControllerMediaMetadata key in the info dictionary which is passed to the UIImagePickerControllerDelegate delegate.
I had a similar question where I wanted just the date a picture was taken and none of the above appear to solve my problem in a simple way (e.g. no external libraries), so here is all of the data I could find which you can extract from an image after selecting it with the picker:
// Inside whatever implements UIImagePickerControllerDelegate
#import AssetsLibrary;
// ... your other code here ...
#implementation MYImagePickerDelegate
- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary *)info
{
NSString *mediaType = info[UIImagePickerControllerMediaType];
UIImage *originalImage = info[UIImagePickerControllerOriginalImage];
UIImage *editedImage = info[UIImagePickerControllerEditedImage];
NSValue *cropRect = info[UIImagePickerControllerCropRect];
NSURL *mediaUrl = info[UIImagePickerControllerMediaURL];
NSURL *referenceUrl = info[UIImagePickerControllerReferenceURL];
NSDictionary *mediaMetadata = info[UIImagePickerControllerMediaMetadata];
NSLog(#"mediaType=%#", mediaType);
NSLog(#"originalImage=%#", originalImage);
NSLog(#"editedImage=%#", editedImage);
NSLog(#"cropRect=%#", cropRect);
NSLog(#"mediaUrl=%#", mediaUrl);
NSLog(#"referenceUrl=%#", referenceUrl);
NSLog(#"mediaMetadata=%#", mediaMetadata);
if (!referenceUrl) {
NSLog(#"Media did not have reference URL.");
} else {
ALAssetsLibrary *assetsLib = [[ALAssetsLibrary alloc] init];
[assetsLib assetForURL:referenceUrl
resultBlock:^(ALAsset *asset) {
NSString *type =
[asset valueForProperty:ALAssetPropertyType];
CLLocation *location =
[asset valueForProperty:ALAssetPropertyLocation];
NSNumber *duration =
[asset valueForProperty:ALAssetPropertyDuration];
NSNumber *orientation =
[asset valueForProperty:ALAssetPropertyOrientation];
NSDate *date =
[asset valueForProperty:ALAssetPropertyDate];
NSArray *representations =
[asset valueForProperty:ALAssetPropertyRepresentations];
NSDictionary *urls =
[asset valueForProperty:ALAssetPropertyURLs];
NSURL *assetUrl =
[asset valueForProperty:ALAssetPropertyAssetURL];
NSLog(#"type=%#", type);
NSLog(#"location=%#", location);
NSLog(#"duration=%#", duration);
NSLog(#"assetUrl=%#", assetUrl);
NSLog(#"orientation=%#", orientation);
NSLog(#"date=%#", date);
NSLog(#"representations=%#", representations);
NSLog(#"urls=%#", urls);
}
failureBlock:^(NSError *error) {
NSLog(#"Failed to get asset: %#", error);
}];
}
[picker dismissViewControllerAnimated:YES
completion:nil];
}
#end
So when you select an image, you get output that looks like this (including date!):
mediaType=public.image
originalImage=<UIImage: 0x7fb38e00e870> size {1280, 850} orientation 0 scale 1.000000
editedImage=<UIImage: 0x7fb38e09e1e0> size {640, 424} orientation 0 scale 1.000000
cropRect=NSRect: {{0, 0}, {1280, 848}}
mediaUrl=(null)
referenceUrl=assets-library://asset/asset.JPG?id=AC072879-DA36-4A56-8A04-4D467C878877&ext=JPG
mediaMetadata=(null)
type=ALAssetTypePhoto
location=(null)
duration=ALErrorInvalidProperty
assetUrl=assets-library://asset/asset.JPG?id=AC072879-DA36-4A56-8A04-4D467C878877&ext=JPG
orientation=0
date=2014-07-14 04:28:18 +0000
representations=(
"public.jpeg"
)
urls={
"public.jpeg" = "assets-library://asset/asset.JPG?id=AC072879-DA36-4A56-8A04-4D467C878877&ext=JPG";
}
Anyway, hopefully that saves someone else some time.
I spend a while working on this as well for an application I was contracted to build. Basically as the API currently stands it is not possible. The basic problem is the UIImage class STRIPS all EXIF data except for the orientation out. Also the function to save to the camera roll strips this data out. So basically the only way to grab and maintain any extra EXIF data is to save it in a private "camera roll" in your application. I have filed this bug with apple as well and emphasized the need to the app reviewer reps we've been in contact with. Hopefully someday they'll add it in.. Otherwise it makes having GEO tagging completely useless as it only works in the "stock" camera application.
NOTE Some applications on the app store hack around this. By, what I have found, directly accessing the camera roll and SAVING photos straight to it to save GEO data. However this only works with the camera roll/saved photos and NOT the rest of the photo library. The photos "synced" to your phone from your computer have all EXIF data except for orientation stripped.
I still can't understand why those applications were approved (heck they even DELETE from the camera roll) and our application which does none of that is still being held back.
For iOS 8 and later you can use Photos Framework.
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
let url = info[UIImagePickerControllerReferenceURL] as? URL
if url != nil {
let fetchResult = PHAsset.fetchAssets(withALAssetURLs: [url!], options: nil)
let asset = fetchResult.firstObject
print(asset?.location?.coordinate.latitude)
print(asset?.creationDate)
}
}
This is something that the public API does not provide, but could be useful to many people. Your primary recourse is to file a bug with Apple that describes what you need (and it can be helpful to explain why you need it as well). Hopefully your request could make it into a future release.
After filing a bug, you could also use one of the Developer Technical Support (DTS) incidents that came with your iPhone Developer Program membership. If there is a public way to do this, an Apple engineer will know. Otherwise, it may at least help get your plight a bit more attention within the mothership. Best of luck!
Use the UIImagePickerControllerMediaURL dictionary key to get the file URL to the original file. Despite what the documentation says, you can get the file URL for photos and not only movies.
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
// Try to get the original file.
NSURL *originalFile = [info objectForKey:UIImagePickerControllerMediaURL];
if (originalFile) {
NSData *fileData = [NSData dataWithContentsOfURL:originalFile];
}
}
You might be able to hash the image data returned by the UIImagePickerController and each of the images in the directory and compare them.
Just a thought, but have you tried TTPhotoViewController in the Three20 project on GitHub?
That provides an image picker that can read from multiple sources. You may be able to use it as an alternative to UIImagePickerController, or the source might give you a clue how to work out how to get the info you need.
Is there a specific reason you want to extract the location data from the image? An alternative could be to get the location separately using the CoreLocation framework. If it's only the geodata you need, this might save you some headaches.
it seems that photo attained by UIImagePickerControllerMediaURL don't have exif tags at all
In order to get this metadata you'll have to use the lower level framework AVFoundation.
Take a look at Apple's Squarecam example (http://developer.apple.com/library/ios/#samplecode/SquareCam/Introduction/Intro.html)
Find the method below and add the line, I've added to the code. The metadata dictionary returned also contains a diagnostics NSDictionary object.
- (BOOL)writeCGImageToCameraRoll:(CGImageRef)cgImage withMetadata:(NSDictionary *)metadata
{
NSDictionary *Exif = [metadata objectForKey:#"Exif"]; // Add this line
}
I'm using this for camera roll images
-(CLLocation*)locationFromAsset:(ALAsset*)asset
{
if (!asset)
return nil;
NSDictionary* pickedImageMetadata = [[asset defaultRepresentation] metadata];
NSDictionary* gpsInfo = [pickedImageMetadata objectForKey:(__bridge NSString *)kCGImagePropertyGPSDictionary];
if (gpsInfo){
NSNumber* nLat = [gpsInfo objectForKey:(__bridge NSString *)kCGImagePropertyGPSLatitude];
NSNumber* nLng = [gpsInfo objectForKey:(__bridge NSString *)kCGImagePropertyGPSLongitude];
if (nLat && nLng)
return [[CLLocation alloc]initWithLatitude:[nLat doubleValue] longitude:[nLng doubleValue]];
}
return nil;
}
-(void) imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
//UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
NSURL *assetURL = [info objectForKey:UIImagePickerControllerReferenceURL];
// create the asset library in the init method of your custom object or view controller
//self.library = [[ALAssetsLibrary alloc] init];
//
[self.library assetForURL:assetURL resultBlock:^(ALAsset *asset) {
// try to retrieve gps metadata coordinates
CLLocation* myLocation = [self locationFromAsset:asset];
// Do your stuff....
} failureBlock:^(NSError *error) {
NSLog(#"Failed to get asset from library");
}];
}
It works obviously if the image contains gps meta informations
Hope it helps
This is in Swift 3 if you still want support for iOS 8:
import AssetsLibrary
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
if picker.sourceType == UIImagePickerControllerSourceType.photoLibrary,
let url = info[UIImagePickerControllerReferenceURL] as? URL {
let assetLibrary = ALAssetsLibrary()
assetLibrary.asset(for: url, resultBlock: { (asset) in
if let asset = asset {
let assetRep: ALAssetRepresentation = asset.defaultRepresentation()
let metaData: NSDictionary = assetRep.metadata() as NSDictionary
print(metaData)
}
}, failureBlock: { (error) in
print(error!)
})
}
}
For iOS 10 - Swift 3
The picker's callback has an info dict where there is a key with metadata: UIImagePickerControllerMediaMetadata
The naughty way to do this is to traverse the UIImagePickerViewController's views and pick out the selected image in the delegate callback.
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
id thumbnailView = [[[[[[[[[[picker.view subviews]
objectAtIndex:0] subviews]
objectAtIndex:0] subviews]
objectAtIndex:0] subviews]
objectAtIndex:0] subviews]
objectAtIndex:0];
NSString *fullSizePath = [[[thumbnailView selectedPhoto] fileGroup] pathForFullSizeImage];
NSString *thumbnailPath = [[[thumbnailView selectedPhoto] fileGroup] pathForThumbnailFile];
NSLog(#"%# and %#", fullSizePath, thumbnailPath);
}
That will give you the path to the full size image, which you can then open with an EXIF library of your choice.
But, this calls a Private API and these method names will be detected by Apple if you submit this app. So don't do this, OK?
Related
UIImagePickerControllerReferenceURL is missing
I'm trying to get some data about a video i chose in the UIImagePicker. So when it gets into the UIImagePicker delegate method (below) i understand i need to use the UIImagePickerControllerReferenceURL key from the info dictionary. -(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { NSURL *url = [info objectForKey:UIImagePickerControllerReferenceURL]; NSLog(#"%#",url); [library addAssetURL:[info objectForKey:UIImagePickerControllerReferenceURL] toAlbum:#"Compedia" withCompletionBlock:^(NSError *error) { if (error!=nil) { NSLog(#"Big error: %#", [error description]); } }]; } The problem is that the url is resulting with nil. I printed the description of info and it looks like this: Printing description of info: { UIImagePickerControllerMediaType = "public.movie"; UIImagePickerControllerMediaURL = "file://localhost/private/var/mobile/Applications/6630FBD3-1212-4ED0-BC3B-0C23AEEFB267/tmp/capture-T0x1f57e880.tmp.Ulfn5o/capturedvideo.MOV"; } After i've done some research i found out that if i set the camera with kUTTypeMovie it should give me both media and reference url. This is how i defined my camera: cameraUI = [[UIImagePickerController alloc] init]; cameraUI.sourceType = UIImagePickerControllerSourceTypeCamera; cameraUI.mediaTypes = [[NSArray alloc] initWithObjects:(NSString *)kUTTypeMovie, nil]; cameraUI.allowsEditing = YES; cameraUI.delegate = delegate; cameraUI.showsCameraControls = NO; cameraUI.cameraOverlayView = [self getCustomToolBar]; Is there something i'm missing? Thanks,
Nearly went insane on this one. Every where I looked everyone seemed to be convinced that the UIImagePickerControllerReferenceURL was always there. Anyway the issue is that if your delegate is being called after you have taken a pic using the camera than the UIImagePickerControllerReferenceURL object will not be there as the image has not been saved to the camera roll yet so therefore there is no UIImagePickerControllerReferenceURL. It is only there when the delegate is called after selecting an image from the camera roll. So my way around this is to use the ALAssetLibrary to save it to the camera roll then read back the URL. Heres my solution: First you want to detect your PickerController sourceType to see if it was the camera or photosLibrary/savedPhotosAlbum. If it is the camera then we use the Asset library's writeImageToSavedPhotosAlbum:orientation:completionBlock: to write the image to the camera roll and give us back the images URL in the completion block. However if the its not the camera then that means is was either the photosLibrary or savedPhotosAlbum which in both cases [info objectForKey:UIImagePickerControllerReferenceURL] would return a valid url - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { //dismiss the imagepicker view [self dismissViewControllerAnimated:YES completion:nil]; if( [picker sourceType] == UIImagePickerControllerSourceTypeCamera ) { UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage]; ALAssetsLibrary *library = [Utils defaultAssetsLibrary]; [library writeImageToSavedPhotosAlbum:image.CGImage orientation:(ALAssetOrientation)image.imageOrientation completionBlock:^(NSURL *assetURL, NSError *error ) { //here is your URL : assetURL }]; } else { //else this is valid : [info objectForKey:UIImagePickerControllerReferenceURL]]; } } Hope that helps.
ALAssetsLibrary is deprecated... use PHFetchOptions.. Refer below answer: https://stackoverflow.com/a/44328085/5315917
How Do I Get The Correct Latitude and Longitude From An Uploaded iPhone Photo?
My problem actually seems rather silly... I am writing an iPhone application that uses MKMapKit. The app grabs the EXIF metadata from a provided geotagged photo. The problem is that the latitude and longitude coordinates that I retrieve, for example: Lat: 34.25733333333334 Lon: 118.5373333333333 returns a location in China. Is there a regional setting that I am missing or do I need to convert the lat/long coordinates before using them? Thank you all in advance for any help you can provide. Here is the code I am using to grab the GPS data. You'll notice I am logging everything to the console so I can see what the values are: void (^ALAssetsLibraryAssetForURLResultBlock)(ALAsset *) = ^(ALAsset *asset) { NSDictionary *metadata = asset.defaultRepresentation.metadata; NSLog(#"Image Meta Data: %#",metadata); NSDictionary *gpsdata = [metadata objectForKey:#"{GPS}"]; self.lat = [gpsdata valueForKey:#"Latitude"]; self.lng = [gpsdata valueForKey:#"Longitude"]; NSLog(#"\nLatitude: %#\nLongitude: %#",self.lat,self.lng); }; NSURL *assetURL = [mediaInfo objectForKey:UIImagePickerControllerReferenceURL]; ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init]; [library assetForURL:assetURL resultBlock:ALAssetsLibraryAssetForURLResultBlock failureBlock:^(NSError *error) { }]; UIImage *img = [mediaInfo objectForKey:#"UIImagePickerControllerEditedImage"]; previewImage.image = nil; self.previewImage.image = img; NSData *imageData = UIImagePNGRepresentation(img); if ([imageData length] > 0) { self._havePictureData = YES; }
i think you should grab the value using following: CLLocation *location = [asset valueForProperty:ALAssetPropertyLocation];
Are you sure you're not missing a minus sign on that 118? 34.257, -118.5373 is nicely inside Los Angeles, California.
While you can get the location from the asset per #Allen, it is also valid to get it from the GPS metadata as you were trying to do initially. I'm not 100% sure the asset library coordinate will be the same as the coord in the GPS metadata, it depends on how Apple stores this coord. For example, if you are using a timestamp, the Asset library timestamp is different than the EXIF creation date (a different topic, admittedly). In any case, the reason you have the coord wrong is b/c you also need to get the direction info as follows: NSDictionary *metadata = asset.defaultRepresentation.metadata; NSLog(#"Image Meta Data: %#",metadata); NSDictionary *gpsdata = [metadata objectForKey:#"{GPS}"]; self.lat = [gpsdata valueForKey:#"Latitude"]; self.lng = [gpsdata valueForKey:#"Longitude"]; // lat is negative is direction is south if ([[gpsdata valueForKey:#"LatitudeRef"] isEqualToString:#"S"]) { self.lat = -self.lat; } // lng is negative if direction is west if ([[gpsdata valueForKey:#"LongitudeRef"] isEqualToString:#"W"]) { self.lng = -self.lng; } NSLog(#"\nLatitude: %#\nLongitude: %#",self.lat,self.lng);
This also will works, void (^ALAssetsLibraryAssetForURLResultBlock)(ALAsset *) = ^(ALAsset *asset) { ALAssetRepresentation *rep = [asset defaultRepresentation]; NSDictionary *metadata = rep.metadata; NSMutableDictionary *GPSDictionary = [[[metadata objectForKey:(NSString *)kCGImagePropertyGPSDictionary]mutableCopy] autorelease]; };
I believe that the reason that there isn't a negative sign is because of the metadata: exif:GPSLongitudeRef: W which (I believe) means that there should be a negative sign in front of the longitude since it is referencing the western hemisphere. I believe that this also applies to the latitude but with exif:GPSLatitudeRef: N for Northern and Southern hemispheres. Hope that this helped. Just realized this is exactly what #XJones said. Metadata using ImageMagick.
How to call UIImagePickerController Delegate programmatically or forcefully [without user interaction]?
In my app, i have Used UiimagepickerController for taking Video.in bettween my programm received any web service Which belongs to my app, i have to stop Video Capture and save video. i have Used StopVideoCapture to do above thing ,but it doesn't call delegate - ` (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info How to force call above delegate ??.or How to handle interruption Handling inUIImagePickerController`.. any idea?
The idea with delegate methods is not that you call those methods - "They call you". So I would not consider calling the delegate method yourself a good practise. However, if you present the UIImagePickerViewController with a modal dialogue (which I guess is common for such a picker) then you can close it like this outside of your delegate method: [[yourPicker parentViewController] dismissModalViewControllerAnimated:YES]; Source Update: You can use the ALAssetsLibrary for accessing the stored data in your iPhone media library. I recently had to do a similar project where I had to list all images on the iPhone. The Github project ELCImagePickerController.git was very useful since it shows how the items in your library can be accessed. So you'll do something like this: #import <AssetsLibrary/AssetsLibrary.h> // .... -(void)fetchPhotoAlbums{ if(!self.assetsGroups){ self.assetsGroups = [NSMutableDictionary dictionary]; } ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init]; NSMutableArray *returnArray = [[NSMutableArray alloc] init]; #autoreleasepool { void (^assetGroupEnumerator)(ALAssetsGroup *, BOOL *) = ^(ALAssetsGroup *group, BOOL *stop){ if (group == nil){ // Completed [self.delegate pictureService:self fetchedAlbums:returnArray]; return; } Album *currentAlbum = [self albumForAssetsGroup:group]; // Store the Group for later retrieving the pictures for the album [self.assetsGroups setObject:group forKey:currentAlbum.identifier]; [returnArray addObject:currentAlbum]; [self.delegate pictureService:self fetchedAlbums:returnArray]; }; void (^assetGroupEnumberatorFailure)(NSError *) = ^(NSError *error) { NSLog(#"A problem occured %#", [error description]); }; [library enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:assetGroupEnumerator failureBlock:assetGroupEnumberatorFailure]; } } -(void)fetchPhotosForAlbum:(Album *)album{ ALAssetsGroup *currentGroup = [self.assetsGroups objectForKey:album.identifier]; NSMutableArray *photos = [NSMutableArray array]; [currentGroup enumerateAssetsUsingBlock:^(ALAsset *asset, NSUInteger index, BOOL *stop){ if(asset == nil){ [self.delegate pictureService:self fetchedPictures:photos forAlbum:album]; return; } [photos addObject:[self pictureForAsset:asset]]; }]; } Additionally I use two mapper methods to convert the AL-classes into my own model classes. - (Album *)albumForAssetsGroup:(ALAssetsGroup *)assetsGroup{ Album *album = [[Album alloc] init]; album.title = [assetsGroup valueForProperty:ALAssetsGroupPropertyName]; album.identifier = [assetsGroup valueForProperty: ALAssetsGroupPropertyPersistentID]; album.assetsCount = assetsGroup.numberOfAssets; album.thumbnail = [UIImage imageWithCGImage:assetsGroup.posterImage]; return album; } - (Picture *)pictureForAsset:(ALAsset *)asset{ Picture *picture = [[Picture alloc]init]; picture.identifier = [((NSArray *)[asset valueForProperty: ALAssetPropertyRepresentations]) objectAtIndex:0]; picture.thumbnail = [UIImage imageWithCGImage:asset.thumbnail]; return picture; } See the AssetsLibrary Documentation
Storing recorded video URL for saving to library later
I have an application where I recording a video. But when the recording is finished, I can't save the video immediately. I need to show an agreement first. So I try to save the URL i get from the image picker. And save the video to the library later. This worked fine in iOS4, but not in iOS5. I'm new to iOS and Objective-C so I probably made some totally wrong declaration of the property that is supposed to hold the URL. This is some of the code: .h #import <UIKit/UIKit.h> #import <AssetsLibrary/AssetsLibrary.h> #interface Video_recViewController : UIViewController <UIImagePickerControllerDelegate, UINavigationControllerDelegate, UIActionSheetDelegate> { NSURL *tempMoviePath; } #property (nonatomic, retain) NSURL *tempMoviePath; .m - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { NSURL *moviePath = [info objectForKey:UIImagePickerControllerMediaURL]; [self dismissModalViewControllerAnimated: YES]; NSLog(#"path from image picker: %#", moviePath); tempMoviePath = moviePath; NSLog(#"temp movie path: %#", tempMoviePath); // [self performSelector:#selector(showAgree) withObject:nil afterDelay:0.5]; } - (void)userAgreed { NSLog(#"user agreed"); //NSLog(#"temp movie path: %#", tempMoviePath); [self saveMyVideo:tempMoviePath]; //[self performSelector:#selector(showSurvey) withObject:nil afterDelay:0.5]; } - (void)saveMyVideo:(NSURL *)videoURL { NSLog(#"saving movie at: %#", videoURL); ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init]; if ([library videoAtPathIsCompatibleWithSavedPhotosAlbum:videoURL]) { [library writeVideoAtPathToSavedPhotosAlbum:videoURL completionBlock:^(NSURL *assetURL, NSError *error){} ]; } [library release]; } Output from log when didFinishPickingMediaWithInfo is: temp movie path: file://localhost/private/var/mobile/Applications/8CFD1CB7-70A0-465C-B730-817ACE5A4F78/tmp/capture-T0x119660.tmp.hNFzkY/capturedvideo.MOV Output from the log when doing "saveMyVideo". The URL has suddenly turn into this!! : saving movie at: ( "0.31269", "0.32899", "0.63999", "0.33001", "0.3", "0.6", "0.15", "0.05999" )
(Answered by the OP in a question edit. See Question with no answers, but issue solved in the comments (or extended in chat) ) The OP wrote: The wrong code was: tempMoviePath = moviePath; Because I'm setting a declared property I must use the set & get methods. It should be: [self setTempMoviePath:moviePath]; Apparently iOS 4 wasn't so hard on this, but iOS5 can't handle it. But, anyway, it was wrong writing like that. I admit my mistake. :)
Write UIImage along with metadata (EXIF, GPS, TIFF) in iPhone's Photo library
I am developing a project, where the requirements are: - User will open the camera through the application - Upon capturing an Image, some data will be appended to the captured image's metadata. I have gone through some of the forums. I tried to code this logic. I guess, I have reached to the point, but something is missing as I am not able to see the metadata that I am appending to the image. My code is: - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)image editingInfo:(NSDictionary *)dictionary { [picker dismissModalViewControllerAnimated:YES]; NSData *dataOfImageFromGallery = UIImageJPEGRepresentation (image,0.5); NSLog(#"Image length: %d", [dataOfImageFromGallery length]); CGImageSourceRef source; source = CGImageSourceCreateWithData((CFDataRef)dataOfImageFromGallery, NULL); NSDictionary *metadata = (NSDictionary *) CGImageSourceCopyPropertiesAtIndex(source, 0, NULL); NSMutableDictionary *metadataAsMutable = [[metadata mutableCopy]autorelease]; [metadata release]; NSMutableDictionary *EXIFDictionary = [[[metadataAsMutable objectForKey:(NSString *)kCGImagePropertyExifDictionary]mutableCopy]autorelease]; NSMutableDictionary *GPSDictionary = [[[metadataAsMutable objectForKey:(NSString *)kCGImagePropertyGPSDictionary]mutableCopy]autorelease]; if(!EXIFDictionary) { //if the image does not have an EXIF dictionary (not all images do), then create one for us to use EXIFDictionary = [NSMutableDictionary dictionary]; } if(!GPSDictionary) { GPSDictionary = [NSMutableDictionary dictionary]; } //Setup GPS dict - //I am appending my custom data just to test the logic…….. [GPSDictionary setValue:[NSNumber numberWithFloat:1.1] forKey:(NSString*)kCGImagePropertyGPSLatitude]; [GPSDictionary setValue:[NSNumber numberWithFloat:2.2] forKey:(NSString*)kCGImagePropertyGPSLongitude]; [GPSDictionary setValue:#"lat_ref" forKey:(NSString*)kCGImagePropertyGPSLatitudeRef]; [GPSDictionary setValue:#"lon_ref" forKey:(NSString*)kCGImagePropertyGPSLongitudeRef]; [GPSDictionary setValue:[NSNumber numberWithFloat:3.3] forKey:(NSString*)kCGImagePropertyGPSAltitude]; [GPSDictionary setValue:[NSNumber numberWithShort:4.4] forKey:(NSString*)kCGImagePropertyGPSAltitudeRef]; [GPSDictionary setValue:[NSNumber numberWithFloat:5.5] forKey:(NSString*)kCGImagePropertyGPSImgDirection]; [GPSDictionary setValue:#"_headingRef" forKey:(NSString*)kCGImagePropertyGPSImgDirectionRef]; [EXIFDictionary setValue:#"xml_user_comment" forKey:(NSString *)kCGImagePropertyExifUserComment]; //add our modified EXIF data back into the image’s metadata [metadataAsMutable setObject:EXIFDictionary forKey:(NSString *)kCGImagePropertyExifDictionary]; [metadataAsMutable setObject:GPSDictionary forKey:(NSString *)kCGImagePropertyGPSDictionary]; CFStringRef UTI = CGImageSourceGetType(source); NSMutableData *dest_data = [NSMutableData data]; CGImageDestinationRef destination = CGImageDestinationCreateWithData((CFMutableDataRef) dest_data, UTI, 1, NULL); if(!destination) { NSLog(#"--------- Could not create image destination---------"); } CGImageDestinationAddImageFromSource(destination, source, 0, (CFDictionaryRef) metadataAsMutable); BOOL success = NO; success = CGImageDestinationFinalize(destination); if(!success) { NSLog(#"-------- could not create data from image destination----------"); } UIImage * image1 = [[UIImage alloc] initWithData:dest_data]; UIImageWriteToSavedPhotosAlbum (image1, self, nil, nil); } Kindly, help me to do this and get something positive. Look at the last line, am I saving the image with my metadata in it? The image is getting saved at that point, but the metadata that I am appending to it, is not getting saved. Thanks in advance.
Apple has updated their article addressing this issue (Technical Q&A QA1622). If you're using an older version of Xcode, you may still have the article that says, more or less, tough luck, you can't do this without low-level parsing of the image data. https://developer.apple.com/library/ios/#qa/qa1622/_index.html I adapted the code there as follows: - (void) saveImage:(UIImage *)imageToSave withInfo:(NSDictionary *)info { // Get the assets library ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init]; // Get the image metadata (EXIF & TIFF) NSMutableDictionary * imageMetadata = [[info objectForKey:UIImagePickerControllerMediaMetadata] mutableCopy]; // add GPS data CLLocation * loc = <•••>; // need a location here if ( loc ) { [imageMetadata setObject:[self gpsDictionaryForLocation:loc] forKey:(NSString*)kCGImagePropertyGPSDictionary]; } ALAssetsLibraryWriteImageCompletionBlock imageWriteCompletionBlock = ^(NSURL *newURL, NSError *error) { if (error) { NSLog( #"Error writing image with metadata to Photo Library: %#", error ); } else { NSLog( #"Wrote image %# with metadata %# to Photo Library",newURL,imageMetadata); } }; // Save the new image to the Camera Roll [library writeImageToSavedPhotosAlbum:[imageToSave CGImage] metadata:imageMetadata completionBlock:imageWriteCompletionBlock]; [imageMetadata release]; [library release]; } and I call this from imagePickerController:didFinishPickingMediaWithInfo: which is the delegate method for the image picker. I use a helper method (adapted from GusUtils) to build a GPS metadata dictionary from a location: - (NSDictionary *) gpsDictionaryForLocation:(CLLocation *)location { CLLocationDegrees exifLatitude = location.coordinate.latitude; CLLocationDegrees exifLongitude = location.coordinate.longitude; NSString * latRef; NSString * longRef; if (exifLatitude < 0.0) { exifLatitude = exifLatitude * -1.0f; latRef = #"S"; } else { latRef = #"N"; } if (exifLongitude < 0.0) { exifLongitude = exifLongitude * -1.0f; longRef = #"W"; } else { longRef = #"E"; } NSMutableDictionary *locDict = [[NSMutableDictionary alloc] init]; [locDict setObject:location.timestamp forKey:(NSString*)kCGImagePropertyGPSTimeStamp]; [locDict setObject:latRef forKey:(NSString*)kCGImagePropertyGPSLatitudeRef]; [locDict setObject:[NSNumber numberWithFloat:exifLatitude] forKey:(NSString *)kCGImagePropertyGPSLatitude]; [locDict setObject:longRef forKey:(NSString*)kCGImagePropertyGPSLongitudeRef]; [locDict setObject:[NSNumber numberWithFloat:exifLongitude] forKey:(NSString *)kCGImagePropertyGPSLongitude]; [locDict setObject:[NSNumber numberWithFloat:location.horizontalAccuracy] forKey:(NSString*)kCGImagePropertyGPSDOP]; [locDict setObject:[NSNumber numberWithFloat:location.altitude] forKey:(NSString*)kCGImagePropertyGPSAltitude]; return [locDict autorelease]; } So far this is working well for me on iOS4 and iOS5 devices. Update: and iOS6/iOS7 devices. I built a simple project using this code: https://github.com/5teev/MetaPhotoSave
The function: UIImageWriteToSavePhotosAlbum only writes the image data. You need to read up on the ALAssetsLibrary The method you ultimately want to call is: ALAssetsLibrary *library = [[ALAssetsLibrary alloc] [library writeImageToSavedPhotosAlbum:metadata:completionBlock];
For anyone who comes here trying to take a photo with the camera in your app and saving the image file to the camera roll with GPS metadata, I have a Swift solution that uses the Photos API since ALAssetsLibrary is deprecated as of iOS 9.0. As mentioned by rickster on this answer, the Photos API does not embed location data directly into a JPG image file even if you set the .location property of the new asset. Given a CMSampleBuffer sample buffer buffer, some CLLocation location, and using Morty’s suggestion to use CMSetAttachments in order to avoid duplicating the image, we can do the following. The gpsMetadata method extending CLLocation can be found here. if let location = location { // Get the existing metadata dictionary (if there is one) var metaDict = CMCopyDictionaryOfAttachments(nil, buffer, kCMAttachmentMode_ShouldPropagate) as? Dictionary<String, Any> ?? [:] // Append the GPS metadata to the existing metadata metaDict[kCGImagePropertyGPSDictionary as String] = location.gpsMetadata() // Save the new metadata back to the buffer without duplicating any data CMSetAttachments(buffer, metaDict as CFDictionary, kCMAttachmentMode_ShouldPropagate) } // Get JPG image Data from the buffer guard let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(buffer) else { // There was a problem; handle it here } // Now save this image to the Camera Roll (will save with GPS metadata embedded in the file) self.savePhoto(withData: imageData, completion: completion) The savePhoto method is below. Note that the handy addResource:with:data:options method is available only in iOS 9. If you are supporting an earlier iOS and want to use the Photos API, then you must make a temporary file and then create an asset from the file at that URL if you want to have the GPS metadata properly embedded (PHAssetChangeRequest.creationRequestForAssetFromImage:atFileURL). Only setting PHAsset’s .location will NOT embed your new metadata into the actual file itself. func savePhoto(withData data: Data, completion: (() -> Void)? = nil) { // Note that using the Photos API .location property on a request does NOT embed GPS metadata into the image file itself PHPhotoLibrary.shared().performChanges({ if #available(iOS 9.0, *) { // For iOS 9+ we can skip the temporary file step and write the image data from the buffer directly to an asset let request = PHAssetCreationRequest.forAsset() request.addResource(with: PHAssetResourceType.photo, data: data, options: nil) request.creationDate = Date() } else { // Fallback on earlier versions; write a temporary file and then add this file to the Camera Roll using the Photos API let tmpURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).appendingPathComponent("tempPhoto").appendingPathExtension("jpg") do { try data.write(to: tmpURL) let request = PHAssetChangeRequest.creationRequestForAssetFromImage(atFileURL: tmpURL) request?.creationDate = Date() } catch { // Error writing the data; photo is not appended to the camera roll } } }, completionHandler: { _ in DispatchQueue.main.async { completion?() } }) } Aside: If you are just wanting to save the image with GPS metadata to your temporary files or documents (as opposed to the camera roll/photo library), you can skip using the Photos API and directly write the imageData to a URL. // Write photo to temporary files with the GPS metadata embedded in the file let tmpURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).appendingPathComponent("tempPhoto").appendingPathExtension("jpg") do { try data.write(to: tmpURL) // Do more work here... } catch { // Error writing the data; handle it here }
A piece of this involves generating the GPS metadata. Here's a category on CLLocation to do just that: https://gist.github.com/phildow/6043486
Getting meta data from cam captured image within an application: UIImage *pTakenImage= [info objectForKey:#"UIImagePickerControllerOriginalImage"]; NSMutableDictionary *imageMetadata = [[NSMutableDictionary alloc] initWithDictionary:[info objectForKey:UIImagePickerControllerMediaMetadata]]; now to save image to library with extracted metadata: ALAssetsLibrary* library = [[ALAssetsLibrary alloc] init]; [library writeImageToSavedPhotosAlbum:[sourceImage CGImage] metadata:imageMetadata completionBlock:Nil]; [library release]; or want to save to local directory CGImageDestinationAddImageFromSource(destinationPath,sourceImage,0, (CFDictionaryRef)imageMetadata);
The problem we are trying to solve is: the user has just taken a picture with the UIImagePickerController camera. What we get is a UIImage. How do we fold metadata into that UIImage as we save it into the camera roll (photo library), now that we don't have the AssetsLibrary framework? The answer (as far as I can make out) is: use the ImageIO framework. Extract the JPEG data from the UIImage, use it as a source and write it and the metadata dictionary into the destination, and save the destination data as a PHAsset into the camera roll. In this example, im is the UIImage and meta is the metadata dictionary: let jpeg = UIImageJPEGRepresentation(im, 1)! let src = CGImageSourceCreateWithData(jpeg as CFData, nil)! let data = NSMutableData() let uti = CGImageSourceGetType(src)! let dest = CGImageDestinationCreateWithData(data as CFMutableData, uti, 1, nil)! CGImageDestinationAddImageFromSource(dest, src, 0, meta) CGImageDestinationFinalize(dest) let lib = PHPhotoLibrary.shared() lib.performChanges({ let req = PHAssetCreationRequest.forAsset() req.addResource(with: .photo, data: data as Data, options: nil) }) A good way to test — and a common use case — is to receive the photo metadata from the UIImagePickerController delegate info dictionary thru the UIImagePickerControllerMediaMetadata key and fold it into the PHAsset as we save it into the photo library.
There are many frameworks that deals with image and metadata. Assets Framework is deprecated, and replaced by Photos Library framework. If you implemented AVCapturePhotoCaptureDelegate to capture photos, you can do so: func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { var metadata = photo.metadata metadata[kCGImagePropertyGPSDictionary as String] = gpsMetadata photoData = photo.fileDataRepresentation(withReplacementMetadata: metadata, replacementEmbeddedThumbnailPhotoFormat: photo.embeddedThumbnailPhotoFormat, replacementEmbeddedThumbnailPixelBuffer: nil, replacementDepthData: photo.depthData) ... } The metadata is a dictionary of dictionaries, and you have to refer to CGImageProperties. I wrote about this topic here.
Here is a slight variation of #matt answer. The following code use only one CGImageDestination and more interesting allow to save in HEIC format on iOS11+. Notice that the compression quality is added to the metadata before adding the image. 0.8 is roughly the compression quality of native camera save. //img is the UIImage and metadata the metadata received from the picker NSMutableDictionary *meta_plus = metadata.mutableCopy; //with CGimage, one can set compression quality in metadata meta_plus[(NSString *)kCGImageDestinationLossyCompressionQuality] = #(0.8); NSMutableData *img_data = [NSMutableData new]; NSString *type; if (#available(iOS 11.0, *)) type = AVFileTypeHEIC; else type = #"public.jpeg"; CGImageDestinationRef dest = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)img_data, (__bridge CFStringRef)type, 1, nil); CGImageDestinationAddImage(dest, img.CGImage, (__bridge CFDictionaryRef)meta_plus); CGImageDestinationFinalize(dest); CFRelease(dest); //image is in img_data //go for the PHLibrary change request