How do I include location data using AVCapturePhoto?
I haven't dealt with the new IOS 11 AVCapturePhoto object so this answer has to make a couple of assumptions about how to access data but in theory all of this should work.
Before adding location data you need to ask the user if you can use their location. Add the "Privacy - Location When In Use" tag to your Info.plist. Then add the following code somewhere in your initialisation.
// Request authorization for location manager
switch CLLocationManager.authorizationStatus() {
case .authorizedWhenInUse:
break
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
locationManagerStatus = CLLocationManager.authorizationStatus()
default:
locationManagerStatus = .denied
}
Now the following will create a dictionary with location information in it.
// create GPS metadata properties
func createLocationMetadata() -> NSMutableDictionary? {
guard CLLocationManager.authorizationStatus() == .authorizedWhenInUse else {return nil}
if let location = locationManager.location {
let gpsDictionary = NSMutableDictionary()
var latitude = location.coordinate.latitude
var longitude = location.coordinate.longitude
var altitude = location.altitude
var latitudeRef = "N"
var longitudeRef = "E"
var altitudeRef = 0
if latitude < 0.0 {
latitude = -latitude
latitudeRef = "S"
}
if longitude < 0.0 {
longitude = -longitude
longitudeRef = "W"
}
if altitude < 0.0 {
altitude = -altitude
altitudeRef = 1
}
let formatter = DateFormatter()
formatter.dateFormat = "yyyy:MM:dd"
gpsDictionary[kCGImagePropertyGPSDateStamp] = formatter.string(from:location.timestamp)
formatter.dateFormat = "HH:mm:ss"
gpsDictionary[kCGImagePropertyGPSTimeStamp] = formatter.string(from:location.timestamp)
gpsDictionary[kCGImagePropertyGPSLatitudeRef] = latitudeRef
gpsDictionary[kCGImagePropertyGPSLatitude] = latitude
gpsDictionary[kCGImagePropertyGPSLongitudeRef] = longitudeRef
gpsDictionary[kCGImagePropertyGPSLongitude] = longitude
gpsDictionary[kCGImagePropertyGPSDOP] = location.horizontalAccuracy
gpsDictionary[kCGImagePropertyGPSAltitudeRef] = altitudeRef
gpsDictionary[kCGImagePropertyGPSAltitude] = altitude
if let heading = locationManager.heading {
gpsDictionary[kCGImagePropertyGPSImgDirectionRef] = "T"
gpsDictionary[kCGImagePropertyGPSImgDirection] = heading.trueHeading
}
return gpsDictionary;
}
return nil
}
This is where I have to do some guessing as I haven't dealt with IOS 11 AVPhotoCapture. You will need a file data representation of your image data. I assume
AVCapturePhoto.fileDataRepresentation()
returns this. Also you will need the original file metadata. I'll take a guess that
AVCapturePhoto.metadata
contains this. With those assumptions the following function will give you a file data representation with additional location data. There may be newer IOS 11 methods to do this in a cleaner way.
func getFileRepresentationWithLocationData(photo : AVCapturePhoto) -> Data {
// get image metadata
var properties = photo.metadata
// add gps data to metadata
if let gpsDictionary = createLocationMetadata() {
properties[kCGImagePropertyGPSDictionary as String] = gpsDictionary
}
// create new file representation with edited metadata
return photo.fileDataRepresentation(withReplacementMetadata:properties,
replacementEmbeddedThumbnailPhotoFormat:photo.embeddedThumbnailPhotoFormat,
replacementEmbeddedThumbnailPixelBuffer:photo.previewPixelBuffer,
replacementDepthData:photo.depthData)
}
Access GPS coordinates from metadata dictionary
I order for the image to have the location, the location service must be active. Then with the following code you can get its coordinates when they exist.
if picker.sourceType == UIImagePickerControllerSourceType.PhotoLibrary
{
if let currentLat = pickedLat as CLLocationDegrees?
{
self.latitude = pickedLat!
self.longitude = pickedLong!
}
else
{
var library = ALAssetsLibrary()
library.enumerateGroupsWithTypes(ALAssetsGroupAll, usingBlock: { (group, stop) -> Void in
if (group != nil) {
println("Group is not nil")
println(group.valueForProperty(ALAssetsGroupPropertyName))
group.enumerateAssetsUsingBlock { (asset, index, stop) in
if asset != nil
{
if let location: CLLocation = asset.valueForProperty(ALAssetPropertyLocation) as CLLocation!
{ let lat = location.coordinate.latitude
let long = location.coordinate.longitude
self.latitude = lat
self.longitude = lat
println(lat)
println(long)
}
}
}
} else
{
println("The group is empty!")
}
})
{ (error) -> Void in
println("problem loading albums: \(error)")
}
}
}
Hope it helps.
From Christian
https://stackoverflow.com/a/26842206/2269679
Save CUSTOM metadata in an image taken from AVFoundation in iOS
UIImage
and CIImage
don't hold all the metadata. In fact, converting it from NSData
to either of those two formats will strip a lot of that metadata out, so IMHO, UIImage
and CIImage
should only be used if you are planning on displaying it in the UI or passing it to CoreImage.
You can read the image properties like this:
- (NSDictionary *)getImageProperties:(NSData *)imageData {
// get the original image metadata
CGImageSourceRef imageSourceRef = CGImageSourceCreateWithData((__bridge CFDataRef) imageData, NULL);
CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(imageSourceRef, 0, NULL);
NSDictionary *props = (__bridge_transfer NSDictionary *) properties;
CFRelease(imageSourceRef);
return props;
}
You can then convert your NSDictionary
to a NSMutableDictionary
:
NSDictionary *props = [self getImageProperties:imageData];
NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:props];
After that, add whatever fields you would like. Note that EXIF data is found by grabbing the kCGImagePropertyExifDictionary
, GPS data by the kCGImagePropertyGPSDictionary
, and so on:
NSMutableDictionary *exifDictionary = properties[(__bridge NSString *) kCGImagePropertyExifDictionary];
Look at the CGImageProperties
header file for all the keys available.
Okay, so now that you have made whatever changes you needed to the metadata, adding it back takes a couple of more steps:
// incoming image data
NSData *imageData;
// create the image ref
CGDataProviderRef imgDataProvider = CGDataProviderCreateWithCFData((__bridge CFDataRef) imageData);
CGImageRef imageRef = CGImageCreateWithJPEGDataProvider(imgDataProvider, NULL, true, kCGRenderingIntentDefault);
// the updated properties from above
NSMutableDictionary *mutableDictionary;
// create the new output data
CFMutableDataRef newImageData = CFDataCreateMutable(NULL, 0);
// my code assumes JPEG type since the input is from the iOS device camera
CFStringRef type = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (__bridge CFStringRef) @"image/jpg", kUTTypeImage);
// create the destination
CGImageDestinationRef destination = CGImageDestinationCreateWithData(newImageData, type, 1, NULL);
// add the image to the destination
CGImageDestinationAddImage(destination, imageRef, (__bridge CFDictionaryRef) mutableDictionary);
// finalize the write
CGImageDestinationFinalize(destination);
// memory cleanup
CGDataProviderRelease(imgDataProvider);
CGImageRelease(imageRef);
CFRelease(type);
CFRelease(destination);
// your new image data
NSData *newImage = (__bridge_transfer NSData *)newImageData;
And there you have it.
Write UIImage along with metadata (EXIF, GPS, TIFF) in iPhone's Photo library
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];
How to write exif metadata to an image (not the camera roll, just a UIImage or JPEG)
UIImage does not contain metadata information (it is stripped). So if you want to save it without using the imagepicker method (not in camera roll):
Follow the answer here to write to a file with the metadata intact:
Problem setting exif data for an image
no idea why would this be downvoted but here is the method:
In this case im getting the image through AVFoundation and this is what goes in the
[[self stillImageOutput] captureStillImageAsynchronouslyFromConnection:videoConnection
completionHandler:^(CMSampleBufferRef imageSampleBuffer, NSError *error)
{
// code here
}
block code:
CFDictionaryRef metaDict = CMCopyDictionaryOfAttachments(NULL, imageSampleBuffer, kCMAttachmentMode_ShouldPropagate);
CFMutableDictionaryRef mutable = CFDictionaryCreateMutableCopy(NULL, 0, metaDict);
// Create formatted date
NSTimeZone *timeZone = [NSTimeZone timeZoneWithName:@"UTC"];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setTimeZone:timeZone];
[formatter setDateFormat:@"HH:mm:ss.SS"];
// Create GPS Dictionary
NSDictionary *gpsDict = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithFloat:fabs(loc.coordinate.latitude)], kCGImagePropertyGPSLatitude
, ((loc.coordinate.latitude >= 0) ? @"N" : @"S"), kCGImagePropertyGPSLatitudeRef
, [NSNumber numberWithFloat:fabs(loc.coordinate.longitude)], kCGImagePropertyGPSLongitude
, ((loc.coordinate.longitude >= 0) ? @"E" : @"W"), kCGImagePropertyGPSLongitudeRef
, [formatter stringFromDate:[loc timestamp]], kCGImagePropertyGPSTimeStamp
, [NSNumber numberWithFloat:fabs(loc.altitude)], kCGImagePropertyGPSAltitude
, nil];
// The gps info goes into the gps metadata part
CFDictionarySetValue(mutable, kCGImagePropertyGPSDictionary, (__bridge void *)gpsDict);
// Here just as an example im adding the attitude matrix in the exif comment metadata
CMRotationMatrix m = att.rotationMatrix;
GLKMatrix4 attMat = GLKMatrix4Make(m.m11, m.m12, m.m13, 0, m.m21, m.m22, m.m23, 0, m.m31, m.m32, m.m33, 0, 0, 0, 0, 1);
NSMutableDictionary *EXIFDictionary = (__bridge NSMutableDictionary*)CFDictionaryGetValue(mutable, kCGImagePropertyExifDictionary);
[EXIFDictionary setValue:NSStringFromGLKMatrix4(attMat) forKey:(NSString *)kCGImagePropertyExifUserComment];
CFDictionarySetValue(mutable, kCGImagePropertyExifDictionary, (__bridge void *)EXIFDictionary);
NSData *jpeg = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer] ;
After this code you will have your image in the jpeg nsdata and the correspoding dictionary for that image in the mutable cfdictionary.
All you have to do now is:
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)jpeg, NULL);
CFStringRef UTI = CGImageSourceGetType(source); //this is the type of image (e.g., public.jpeg)
NSMutableData *dest_data = [NSMutableData data];
CGImageDestinationRef destination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)dest_data,UTI,1,NULL);
if(!destination) {
NSLog(@"***Could not create image destination ***");
}
//add the image contained in the image source to the destination, overidding the old metadata with our modified metadata
CGImageDestinationAddImageFromSource(destination,source,0, (CFDictionaryRef) mutable);
//tell the destination to write the image data and metadata into our data object.
//It will return false if something goes wrong
BOOL success = CGImageDestinationFinalize(destination);
if(!success) {
NSLog(@"***Could not create data from image destination ***");
}
//now we have the data ready to go, so do whatever you want with it
//here we just write it to disk at the same path we were passed
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0]; // Get documents folder
NSString *dataPath = [documentsDirectory stringByAppendingPathComponent:@"ImagesFolder"];
NSError *error;
if (![[NSFileManager defaultManager] fileExistsAtPath:dataPath])
[[NSFileManager defaultManager] createDirectoryAtPath:dataPath withIntermediateDirectories:NO attributes:nil error:&error]; //Create folder
// NSString *imageName = @"ImageName";
NSString *fullPath = [dataPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.jpg", name]]; //add our image to the path
[dest_data writeToFile:fullPath atomically:YES];
//cleanup
CFRelease(destination);
CFRelease(source);
Note how I'm not saving using the ALAssets but directly into a folder of my choice.
Btw most of this code can be found in the link I posted at first.
Problem setting exif data for an image
The following blog post is where I got my answer when I had issues with modifying and saving Exif data Caffeinated Cocoa. This works on iOS.
Here is my test code for writing Exif and GPS data. It pretty much a copy and paste of the code from the above blog. I am using this to write exif data to a captured image.
NSData *jpeg = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer] ;
CGImageSourceRef source ;
source = CGImageSourceCreateWithData((CFDataRef)jpeg, NULL);
//get all the metadata in the image
NSDictionary *metadata = (NSDictionary *) CGImageSourceCopyPropertiesAtIndex(source,0,NULL);
//make the metadata dictionary mutable so we can add properties to it
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
[GPSDictionary setValue:[NSNumber numberWithFloat:_lat] forKey:(NSString*)kCGImagePropertyGPSLatitude];
[GPSDictionary setValue:[NSNumber numberWithFloat:_lon] forKey:(NSString*)kCGImagePropertyGPSLongitude];
[GPSDictionary setValue:lat_ref forKey:(NSString*)kCGImagePropertyGPSLatitudeRef];
[GPSDictionary setValue:lon_ref forKey:(NSString*)kCGImagePropertyGPSLongitudeRef];
[GPSDictionary setValue:[NSNumber numberWithFloat:_alt] forKey:(NSString*)kCGImagePropertyGPSAltitude];
[GPSDictionary setValue:[NSNumber numberWithShort:alt_ref] forKey:(NSString*)kCGImagePropertyGPSAltitudeRef];
[GPSDictionary setValue:[NSNumber numberWithFloat:_heading] forKey:(NSString*)kCGImagePropertyGPSImgDirection];
[GPSDictionary setValue:[NSString stringWithFormat:@"%c",_headingRef] forKey:(NSString*)kCGImagePropertyGPSImgDirectionRef];
[EXIFDictionary setValue:xml 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); //this is the type of image (e.g., public.jpeg)
//this will be the data CGImageDestinationRef will write into
NSMutableData *dest_data = [NSMutableData data];
CGImageDestinationRef destination = CGImageDestinationCreateWithData((CFMutableDataRef)dest_data,UTI,1,NULL);
if(!destination) {
NSLog(@"***Could not create image destination ***");
}
//add the image contained in the image source to the destination, overidding the old metadata with our modified metadata
CGImageDestinationAddImageFromSource(destination,source,0, (CFDictionaryRef) metadataAsMutable);
//tell the destination to write the image data and metadata into our data object.
//It will return false if something goes wrong
BOOL success = NO;
success = CGImageDestinationFinalize(destination);
if(!success) {
NSLog(@"***Could not create data from image destination ***");
}
//now we have the data ready to go, so do whatever you want with it
//here we just write it to disk at the same path we were passed
[dest_data writeToFile:file atomically:YES];
//cleanup
CFRelease(destination);
CFRelease(source);
Related Topics
Recognizing Touch Events Only Inside the Masked View
How to Handle Oauth-Style Log Ins with Imessage Apps in iOS 10, Xcode 8
How to Access My Swift Classes from My UI Tests
Restkit, Coredata and Swift - I Can't Seem to Fetch Results Back Out
Saving Contact Address to Unified Contact Results in (Cnerrordomain Error 500)
How to Bridge Nsnumber to Float in JSON Parsing
Core Data Transient Values with Swift
How to Eliminate the Margin on the Left Side of a Uitableview, Without Creating a Gap on the Right
How to Determine If a Business Is Open Given the Hours of Operation (Swift-Ios)
Display UI (Storyboard Possibly) with Broadcast UI Extension
How to Call Secitemcopymatching in Xcode 7 Beta 4
Swift 4 Get Error Code from Error
Moya/Alamofire - Url Encoded Params with Same Keys
(Swift Spritekit) Rotate Sprite in the Direction of Touch