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