Share Extension: Grab thumbnail in custom view controller like SLComposeServiceViewController - swift

I'm trying to grab thumbnail image from a website so I can paste it on my custom UIViewController for share extension. I know SLComposeServiceViewController does this for free, but I have to make a customized view controller.
Is there any way to do this with existing APIs?
Thanks.

I also hit the limit in customizing SLComposeServiceViewController and had to create own preview.
Basic approach is like this:
for (NSExtensionItem *item in self.extensionContext.inputItems)
{
for (NSItemProvider *itemProvider in item.attachments)
{
//kUTTypeVCard, kUTTypeURL, kUTTypeImage, kUTTypeQuickTimeMovie
NSString *typeIdentifier = (__bridge NSString *)kUTTypeImage;
if ([itemProvider hasItemConformingToTypeIdentifier:typeIdentifier])
{
[itemProvider loadPreviewImageWithOptions:nil completionHandler:^(UIImage *image, NSError *error)
{
if (image)
{
//Use image
}
}];
}
}
}
Please note that
- (void)loadPreviewImageWithOptions:(NSDictionary *)options completionHandler:(NSItemProviderCompletionHandler)completionHandler
Loads the preview image for this item by either calling the supplied preview block or falling back to a QuickLook-based handler. This method, like loadItemForTypeIdentifier:options:completionHandler:, supports implicit type coercion for the item parameter of the completion block. Allowed value classes are: NSData, NSURL, UIImage/NSImage.

Try this code, to get a thumbnail from file URL:
NSURL *path = self.url;
NSDictionary *options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO] forKey:(NSString *)kQLThumbnailOptionIconModeKey];
CGImageRef ref = QLThumbnailImageCreate(kCFAllocatorDefault, (__bridge CFURLRef)path, CGSizeMake(600, 800 /* Or whatever size you want */), (__bridge CFDictionaryRef)options);
NSImage *thunbnail = [[NSImage alloc]initWithCGImage:ref size:NSZeroSize];

Related

Getting Youtube Channel Playlist using Objective-C API

I'm trying to use Google's Objective-C Youtube APIs to fetch a youtube channel's playlist - with no luck.
-I downloaded Google's official API from:
http://code.google.com/p/gdata-objectivec-client/source/browse/#svn%2Ftrunk%2FExamples%2FYouTubeSample
But the sample App doesn't really do anything - its not even an iOS sample App. Seems to be a Mac OS App. Its Read-Me file says: "This sample should automatically build and copy over the GTL.framework as part of the build-and-run process."
Ok... and then what?
How do you get this to work in an iPhone App?
I haven't found any actual instructions to make this work.
Any idea what we're supposed to do here?
you can try source code at this path
https://bitbucket.org/eivvanov/youtubedemo/overview
I have spent a day and a half trying to figure it out on how to use the MAC OSX app they have given as an example. I ended up with an iPhone app which I manage to build to get all the Uploaded video I have from YouTube.
Link: YouTubeProject
In order to make it work:
You have to add the GData project from google
In the LTMasterViewController.m-> (GDataServiceGoogleYouTube *)youTubeService: put your username and password
The "gdata-objectivec-client" for youtube been superseded by a JSON-API Link. Scroll down to youtube.
For supporting the JSON-API here is the details Link.
And for fetching the playlist have a look at the Link.
For total newbies who are lost : consider a sample function that will help understand the entire cycle of fetch,parse,display etc and bring youtube channel's videos to your tableview specifically. im not writing the tableview part here
-(void)initiateRequestToYoutubeApiAndGetChannelInfo
{
NSString * urlYouCanUseAsSample = #"https://www.googleapis.com/youtube/v3/search?key={YOUR_API_KEY_WITHOUT_CURLY_BRACES}&channelId={CHANNEL_ID_YOU_CAN_GET_FROM_ADDRESS_BAR_WITHOUT_CURLY_BRACES}&part=snippet,id&order=date&maxResults=20";
NSURL *url = [[NSURL alloc] initWithString: urlYouCanUseAsSample];
// Create your request
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// Send the request asynchronously remember to reload tableview on global thread
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
// Callback, parse the data and check for errors
if (data && !connectionError) {
NSError *jsonError;
NSDictionary *jsonResult = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&jsonError];
if (!jsonError) {
// better put a breakpoint here to see what is the result and how it is brought to you. Channel id name etc info should be there
NSLog(#"%#",jsonResult);
/// separating "items" dictionary and making array
//
id keyValuePairDict = jsonResult;
NSMutableArray * itemList = keyValuePairDict[#"items"];
for (int i = 0; i< itemList.count; i++) {
/// separating VIDEO ID dictionary from items dictionary and string video id
id v_id0 = itemList[i];
NSDictionary * vid_id = v_id0[#"id"];
id v_id = vid_id;
NSString * video_ID = v_id[#"videoId"];
//you can fill your local array for video ids at this point
// [video_IDS addObject:video_ID];
/// separating snippet dictionary from itemlist array
id snippet = itemList[i];
NSDictionary * snip = snippet[#"snippet"];
/// separating TITLE and DESCRIPTION from snippet dictionary
id title = snip;
NSString * title_For_Video = title[#"title"];
NSString * desc_For_Video = title[#"description"];
//you can fill your local array for titles & desc at this point
// [video_titles addObject:title_For_Video];
// [video_description addObject:desc_For_Video];
/// separating thumbnail dictionary from snippet dictionary
id tnail = snip;
NSDictionary * thumbnail_ = tnail[#"thumbnails"];
/// separating highresolution url dictionary from thumbnail dictionary
id highRes = thumbnail_;
NSDictionary * high_res = highRes[#"high"];
/// separating HIGH RES THUMBNAIL IMG URL from high res dictionary
id url_for_tnail = high_res;
NSString * thumbnail_url = url_for_tnail[#"url"];
//you can fill your local array for titles & desc at this point
[video_thumbnail_url addObject:thumbnail_url];
}
// reload your tableview on main thread
//[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
performSelectorOnMainThread:#selector(reloadInputViews) withObject:nil waitUntilDone:NO];
// you can log all local arrays for convenience
// NSLog(#"%#",video_IDS);
// NSLog(#"%#",video_titles);
// NSLog(#"%#",video_description);
// NSLog(#"%#",video_thumbnail_url);
}
else
{
NSLog(#"an error occurred");
}
}
}];
}

iOS - Returning a variable from a block

I have an iOS project which is pulling images from a datasource method. I would like to be able to pull the image from the assets library (and the code chunk below does this just fine).
However, I need this dataSource method to return a UIImage, but when I use the assets library methods to get the image, the image is returned in a result block. Simply putting return image in the result block obviously does not work.
Does anyone have any idea how I can have the method return a UIImage from inside the result block? I have seen several other SO questions about returning images within blocks, but they are say to call another method. I - unfortunately - can't do that because this method is a nimbus datasource method which must return a UIImage.
Any help or advice would be greatly appreciated! Code below:
- (UIImage *)photoAlbumScrollView: (NIPhotoAlbumScrollView *)photoAlbumScrollView
photoAtIndex: (NSInteger)photoIndex
photoSize: (NIPhotoScrollViewPhotoSize *)photoSize
isLoading: (BOOL *)isLoading
originalPhotoDimensions: (CGSize *)originalPhotoDimensions {
__block UIImage *image = nil;
ALAssetsLibrary* assetslibrary = [[ALAssetsLibrary alloc] init];
[assetslibrary assetForURL:[_photos objectAtIndex:photoIndex]
resultBlock:^(ALAsset *asset){
ALAssetRepresentation *rep = [asset defaultRepresentation];
CGImageRef imageRef = [rep fullScreenImage];
if (imageRef) {
image = [UIImage imageWithCGImage:imageRef];
}
}
failureBlock:^(NSError *error) {
//return nil;
}];
return image;
}
You should create an array for each image. When this data source method Is first called, you will not have an image for that index in the array. Kick off the asset call then return a place holder image. When the block returns, replace the place holder image with the asset image returned in the block. You may need to perform this on the main queue using GCD.
So I think I have a solution to your problem. The idea is to make use of a dispatch_group, since you can wait on a dispatch group - it gives you a way to block a thread until something happens. It may require that your datasource action NOT use the mainthread, but you are going to have to play with this. Lets assume that the object implementing photoAlbumScrollView is called 'obj'.
obj creates a serial dispatch queue (called queue)
datasource sends [obj photoAlbumScrollView] message
photoAlbumScrollView does what it does now, but before returning waits on the queue
the final block unblocks the queue letting the group finish
The code:
__block UIImage *image = nil;
ALAssetsLibrary* assetslibrary = [[ALAssetsLibrary alloc] init];
dispatch_queue_t queue = dispatch_queue_create("com.myApp.assetFetch", DISPATCH_QUEUE_SERIAL);
[assetslibrary assetForURL:[_photos objectAtIndex:photoIndex]
resultBlock:^(ALAsset *asset){
ALAssetRepresentation *rep = [asset defaultRepresentation];
CGImageRef imageRef = [rep fullScreenImage];
if (imageRef) {
image = [UIImage imageWithCGImage:imageRef];
}
dispatch_resume(queue);
}
failureBlock:^(NSError *error) {
dispatch_resume(queue);
}];
dispatch_suspend(queue);
dispatch_sync(queue, ^{ NSLog(#"UNSUSPEND!"); }); // ultimately a block with just a ';' in it
dispatch_release(queue);
return image;
I obviously did not test this but it or something close to it should work, assuming again that you can make this on a thread and not on the mainThread.

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

iPhone: Can't set object received from thread in NSMutableDictionary

I've got a thread (specifically an NSOperation) that runs to load some images for me for a scroll view when my main view asks for them. Any number of these NSOperations can be queued at once. So it goes with the filepath I give it and loads the image from the disk (as UIImages) and then sends the object back to my mainview by using performSelectorOnMainThread: and passing my mainview an NSDictionary of the object, and an image ID value. My main view is then supposed to insert the image object and the image ID string into an NSMutableDictionary that it has for the mainview to be able to use. I've verified that the NSMutableDictionary is allocated and initialized fine, but when the method the NSOperation calls tries to add the objects to the dictionary nothing happens. I've verified that the object and string i get from the dictionary the thread sent me are not null or anything but yet it doesn't work. Am I not doing something right or using a bad technique? What would anyone suggest to do in a situation like this where I need to add UIImages to an NSMutableDictionary from a thread? Thanks so much!
Here's the NSOperation code I use:
- (void)main {
NSString *filePath = [applicaitonAPI getFilePathForCachedImageWithID:imageID andSize:imageSize];
UIImage *returnImage = [[UIImage alloc] initWithContentsOfFile:filePath];
if (returnImage) {
NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithCapacity:2];
[dict setObject:returnImage forKey:#"IMAGE"];
[dict setValue:[NSString stringWithFormat:#"%d", imageID] forKey:#"IMAGE_ID"];
NSDictionary *returnDict = [[NSDictionary alloc] initWithDictionary:dict];
[dict release];
[mainViewController performSelectorOnMainThread:#selector(imageLoaderLoadedImage:) withObject:returnDict waitUntilDone:NO];
[returnDict release];
}
}
And here's the method on the main thread:
- (void)imageLoaderLoadedImage:(NSDictionary *)dict {
UIImage *loadedImage = [dict objectForKey:#"IMAGE"];
NSString *loadedImage = [dict valueForKey:#"IMAGE_ID"];
[imagesInMemoryDictionary setObject:loadedImage forKey:loadedImageID];
[self drawItemsToScrollView];
}
[mainViewController performSelectorOnMainThread:#selector(imageLoaderLoadedImage:) withObject:nil waitUntilDone:NO];
You're not passing returnDict as the parameter to the method. You're passing nil.
A couple of other thoughts:
you don't need to create returnDict. You can just use dict as the method parameter.
you're leaking returnImage.
edit
Since you apparently are passing returnDict as the parameter to the method, my other guess would be that mainViewController is nil. Other than that, your code looks functional.

UIImagePickerController and extracting EXIF data from existing photos

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?