How to modify image metadata ( EXIF ) in iOS without duplicating? - ios

Currently the code I am using can write the updated metadata but creates a duplicate image. Here is the code :
if( [self.textView.text length] != 0 && ![self.userComments isEqualToString: self.textView.text])
{
// This code works but creates a duplicate image
NSMutableDictionary *userCommentDictionary = [NSMutableDictionary dictionary];
[userCommentDictionary setValue:self.textView.text forKey:(NSString *)kCGImagePropertyExifUserComment];
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setValue:userCommentDictionary forKey:(NSString *)kCGImagePropertyExifDictionary];
ALAssetsLibrary *al = [[ALAssetsLibrary alloc] init];
[al writeImageToSavedPhotosAlbum:[self.imageView.image CGImage]
metadata:dict
completionBlock:^(NSURL *assetURL, NSError *error) {
if (error == nil) {
NSLog(#"Image saved.");
self.userComments = self.textView.text;
} else {
NSLog(#"Error saving image.");
}
}];
}
Is there anyway to avoid duplication ?
Thanks for your time

As noted in the comment, I don't believe this is possible.
AssetsLibrary doesn't allow modifying of the original asset at all, everything is saved as a new asset with a reference to the original.
With the new PhotoKit library in iOS 8 they do allow modifying of the asset, but I do not see anything there that allows you to modify the metadata either.
Taking a glance at ImageIO there are methods to modify metadata, but again, nothing to save it to the photo library.
With this, however, you could probably replace a file on disk with another one with modified exif data.
edit to elaborate:
According to answers here it seems like ALAssets provide a URL that does not point to the disk. I believe that means that you have no way of getting the acutual URL of the image to overwrite it, though not in the photos library.
I would suggest you file this as an enhancement to Apple if its that important, if many people request the same thing, they might add it in the future! It does seem that they don't want people messing with this stuff though..

Related

How to generate thumbnails of images on iCloud Drive?

This appears easy, but the lack of documentation makes this question impossible to guess.
I have pictures and videos on my app's icloud drive and I want to create thumbnails of these assets. I am talking about assets on iCloud Drive, not the iCloud photo stream inside the camera roll. I am talking about the real iCloud Drive folder.
Creating thumbnails from videos are "easy" compared to images. You just need 2 weeks to figure out how it works, having in mind the poor documentation Apple wrote but thumbnails from images seem impossible.
What I have now is an array of NSMetadataItems each one describing one item on the iCloud folder.
These are the methods I have tried so far that don't work:
METHOD 1
[fileURL startAccessingSecurityScopedResource];
NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] init];
__block NSError *error;
[coordinator coordinateReadingItemAtURL:fileURL
options:NSFileCoordinatorReadingImmediatelyAvailableMetadataOnly
error:&error
byAccessor:^(NSURL *newURL) {
NSDictionary *thumb;
BOOL success = [newURL getResourceValue:&thumb forKey:NSURLThumbnailDictionaryKey error:&error];
UIImage *thumbnail = thumb[NSThumbnail1024x1024SizeKey];
}];
[fileURL stopAccessingSecurityScopedResource];
The results of this method are fantastic. Ready for that? Here we go: success = YES, error = nil and thumbnail = nil.
ANOTHER METHOD
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:fileURL
options:nil];
AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:asset];
imageGenerator.appliesPreferredTrackTransform = YES;
CMTime time = CMTimeMake(0, 60); // time range in which you want
NSValue *timeValue = [NSValue valueWithCMTime:time];
[imageGenerator generateCGImagesAsynchronouslyForTimes:#[timeValue] completionHandler:^(CMTime requestedTime, CGImageRef image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError * error) {
thumbnail = [[UIImage alloc] initWithCGImage:image];
}];
error = The requested URL was not found on this server. and thumbnail = nil
This method appears to be just for videos. I was trying this just in case. Any equivalent of this method to images?
PRIMITIVE METHOD
NSData *tempData = [NSData dataWithContentsOfUrl:tempURL];
NOPE - data = nil
METHOD 4
The fourth possible method would be using ALAsset but this was deprecated on iOS 9.
I think that all these methods fail because they just work (bug or not) if the resource is local. Any ideas on how to download the image so I can get the thumbnail?
Any other ideas?
thanks
EDIT: after several tests I see that Method 1 is the only one that seems to be in the right direction. This method works poorly, sometimes grabbing the icon but most part of the time not working.
Another point is this. Whatever people suggests me, they always say about downloading the whole image to get the thumbnail. I don't think this is the way to go. Just see how getting thumbnails of video work. You don't download the whole video to get its thumbnail.
So this
question remains open.
The Photos-Framework or AssetsLibrary will not work here as you would have to import your iCloud Drive Photos first to the PhotoLibrary to use any methods of these two frameworks.
What you should look at is ImageIO:
Get the content of the iCloud Drive Photo as NSData and then proceed like this:
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)(imageData), NULL );
NSDictionary* thumbOpts = [NSDictionary dictionaryWithObjectsAndKeys:
(id)kCFBooleanTrue, (id)kCGImageSourceCreateThumbnailWithTransform,
(id)kCFBooleanTrue, (id)kCGImageSourceCreateThumbnailFromImageAlways,
[NSNumber numberWithInt:160],(id)kCGImageSourceThumbnailMaxPixelSize,
nil];
CGImageRef thumbImageRef = CGImageSourceCreateThumbnailAtIndex(source,0,(__bridge CFDictionaryRef)thumbOpts);
UIImage *thumbnail = [[UIImage alloc] initWithCGImage: thumbImageRef];
After testing several solutions, the one that seems to work better is this one:
[fileURL startAccessingSecurityScopedResource];
NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] init];
__block NSError *error;
[coordinator coordinateReadingItemAtURL:fileURL
options:NSFileCoordinatorReadingImmediatelyAvailableMetadataOnly
error:&error
byAccessor:^(NSURL *newURL) {
NSDictionary *thumb;
BOOL success = [newURL getResourceValue:&thumb forKey:NSURLThumbnailDictionaryKey error:&error];
UIImage *thumbnail = thumb[NSThumbnail1024x1024SizeKey];
}];
[fileURL stopAccessingSecurityScopedResource];
This solution is not perfect. It will fail to bring the thumbnails sometimes but I was not able to find any other solution that works 100%. Others are worst than that.
This works for me.It has a little bit different
func genereatePreviewForOnce(at size: CGSize,completionHandler: #escaping (UIImage?) -> Void) {
_ = fileURL.startAccessingSecurityScopedResource()
let fileCoorinator = NSFileCoordinator.init()
fileCoorinator.coordinate(readingItemAt: fileURL, options: .immediatelyAvailableMetadataOnly, error: nil) { (url) in
if let res = try? url.resourceValues(forKeys: [.thumbnailDictionaryKey]),
let dict = res.thumbnailDictionary {
let image = dict[.NSThumbnail1024x1024SizeKey]
completionHandler(image)
} else {
fileURL.removeCachedResourceValue(forKey: .thumbnailDictionaryKey)
completionHandler(nil)
}
fileURL.stopAccessingSecurityScopedResource()
}
}
It looks like you are generating your thumbnail after the fact. If this is your document and you are using UIDocument, override fileAttributesToWriteToURL:forSaveOperation:error: to insert the thumbnail when the document is saved.

iOS Photos framework unable to get image data / UTI

When requesting images from the Photos Framework I manage to get all but the last 64 correctly. The last ones always return nil for the dataUTI and imageData in the following code. Whilst attempting to figure out what was going on I found that the PHAsset knows exactly what the UTI is, but is reporting it to me as nil.
Anyone else seen this?
You can see I've made my code access the asset's UTI when it's reported as nil so that my app can determine if it's a gif or not but this isn't an advisable way of doing it and I never get the imageData anyway so it's not a huge amount of help!
PHFetchOptions* fetchOptions = [[PHFetchOptions alloc] init];
fetchOptions.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"creationDate" ascending:NO]];
PHFetchResult *allPhotosResult = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeImage options: fetchOptions];
[allPhotosResult enumerateObjectsUsingBlock:^(PHAsset *asset, NSUInteger idx, BOOL *stop) {
PHImageRequestOptions* options = [[PHImageRequestOptions alloc] init];
options.synchronous = NO;
options.networkAccessAllowed = YES;
options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
[[PHImageManager defaultManager] requestImageDataForAsset: asset options: options resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
NSString* val = [asset valueForKey: #"uniformTypeIdentifier"];
if( !dataUTI )
{
dataUTI = val;
}
}];
}];
EDIT:
I forgot to mention that the missing image creation dates aren't the most recent images and seem spread out. Actually, even the Photos app doesn't seem to show them, based on their creation date. But there doesn't seem to be anything that should be in that their positions looking at the neighboring images of where their creation dates would place them.
Not much of an answer here so happy for someone else to take a bash at explaining it!
Looking at the creation dates of the missing assets I managed to track one down in the Photos app that was missing from my app. It had a thumbnail but when I selected it it did the circular download indicator to pull down the data but then trying to open it in my app's Action Extension (just let's you preview the gif's animation in the Photos app or elsewhere) a popup appeared that said there was an error preparing it. Which I've not seen before but clearly something was going wonky with iCloud.
Previously I was requesting the PHImageRequestOptionsVersionUnadjusted in my app but switching it to PHImageRequestOptionsVersionOriginal seems to have fixed it....?

iOS - Is it possible to rename image file name before saving it into iPhone device gallery from app?

Hey I'm new to iPhone and I have been trying to make an gallery kind of app. Basically, what I want to do is that i need to save all the captured images into a specific folder like a new album "My_App Images" related to our app name in iPhone device gallery, it's working for me, but I am having trouble to change the image file name, i don't know that Is it possible to specify a file name? Using iPhoto, currently i am getting image file name as "IMG_0094.jpg", can we change it with any other file name like "Anyfilename.png" format programmatically?
here is my code for saving images to the specific album :
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)image editingInfo:(NSDictionary *)editingInfo
{
[self.library saveImage:image toAlbum:#"My_App Images" withCompletionBlock:^(NSError *error) {
if (error!=nil) {
NSLog(#"Image saving error: %#", [error description]);
}
}];
[picker dismissViewControllerAnimated:NO completion:nil];
}
Any source or link for reference is appreciated. Thanks for the help!
There is a way to kinda do that, by setting the image IPTC metadata field "Object Name". If you later import the image to iPhoto, then this name will be used as its title.
See details (and code) at http://ootips.org/yonat/how-to-set-the-image-name-when-saving-to-the-camera-roll/ .
Do you meant,
// Build NSData in memory from the btnImage...
NSData* imageData = UIImageJPEGRepresentation(image, 1.0);
// Save to the default Apple (Camera Roll) folder.
[imageData writeToFile:#"/private/var/mobile/Media/DCIM/100APPLE/customImageFilename.jpg" atomically:NO];
Now adjust the path of folder as per your folder name...
Sorry to disappoint you, but it seems that you can not change the name of the photos, before or after saving, in the photo album, custom or not. Here is a post to explain it:
iOS rename/delete albums of photos
Edit
So, to clarify my comment, use the following override:
Download the NSMutableDictionary category for metadata of image here.
Also download the sample project CustomAlbumDemo from here and modify the NSMutableDictionary+ImageMetadata.m file in the CustomAlbumDemo project as:
-(void)saveImage:(UIImage*)image toAlbum:(NSString*)albumName withCompletionBlock:(SaveImageCompletion)completionBlock
{
//write the image data to the assets library (camera roll)
NSData* imageData = UIImageJPEGRepresentation(image, 1.0);
NSMutableDictionary *metadata = [[NSMutableDictionary alloc] init];
[metadata setDescription:#"This is my special image"];
[self writeImageDataToSavedPhotosAlbum:imageData metadata:metadata completionBlock:^(NSURL *assetURL, NSError *error) {
//error handling
if (error!=nil) {
completionBlock(error);
return;
}
//add the asset to the custom photo album
[self addAssetURL: assetURL
toAlbum:albumName
withCompletionBlock:completionBlock];
}];
}

UIManagedDocument does not save to disk

I followed the Stanford iOS 7 course of Fall 2013 and I'm getting used to all the concepts, though I encounter a problem with Core Data's UIManagedDocument and persistent saving.
My application is similar to the courses one, and all the Model/Object works fine and displays correctly. The code does not differ much from the provided material; all with instantiation, file handling, object context and that stuff and with some NSLog debugging and manual save control I made sure, the context saves all the changes made to it (e.g. via the notification the UIManagedDocument fires).
What does NOT work is keeping that stuff on file. I mean, it does not save it to file, though it says it saves the context. I thought that was related to autosaving, so I created a Button in the UI to manually do that. But still no persistent save. I have no idea how that can be. I checked the storage first with the App with the parts that actually add objects to the document being disabled. Then I moved on to use a third party app that can display the contents of an apps storage. The structure is there, but no saved data.
I came across a website, where someone said that can be related to missing any required values in the model when setting the objects values. Not the case, tested with a subclass of UIManagedDocument with that handleError: thing.
Anyone else got this? Data structure is fairly simple, just one entitity with about 6 strings and one other type of value. I don't want to post all the code here, since it's a lot. If you have a suspicion on what it can be I can post parts of the code.
Code of creating the document in AppDelegate.m:
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *documentsDirectory = [[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *url = [documentsDirectory URLByAppendingPathComponent:#"AppDocument"];
DebuggingManagedDocument *document = [[DebuggingManagedDocument alloc] initWithFileURL:url];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
document.persistentStoreOptions = options;
if ( [fileManager fileExistsAtPath:[url path]] ) {
[document openWithCompletionHandler:^(BOOL success){
if ( !success ) {
NSLog(#"Could not open document.. :(");
} else {
[self documentIsReady:document];
}
}];
} else {
// Create
[document saveToURL:url forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success){
if ( !success ) {
NSLog(#"Could not save document... :(");
} else {
NSLog(#"Saved document!!");
[self documentIsReady:document];
}
}];
}
Code of
Regards
Try using the JOURNAL=DELETE mode persistentStoreCoordinator option instead of the default WAL mode.

Memory problems while reading iphone camera roll with AlAssetsLibrary

I'm trying to get last picture from iphone camera roll. I use the following code:
UIImage* __block image = [[UIImage alloc] init];
ALAssetsLibrary* library = [[ALAssetsLibrary alloc] init];
[library enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup* group, BOOL* stop) {
[group setAssetsFilter:[ALAssetsFilter allPhotos]];
if ([group numberOfAssets] > 0) {
[group enumerateAssetsAtIndexes:[NSIndexSet indexSetWithIndex:[group numberOfAssets]-1] options:NSEnumerationConcurrent usingBlock:^(ALAsset* alAsset, NSUInteger index, BOOL* innerStop) {
if (alAsset) {
ALAssetRepresentation* rawImage = [alAsset defaultRepresentation];
image = [UIImage imageWithCGImage:[rawImage fullScreenImage]];
[self doTheJobWithImage:image];
// Inside doTheJobWithImage: a segue is also performed at the end
}
}];
}
} failureBlock:^(NSError* error) {
NSLog(#"Error: %#", [error localizedDescription]);
}];
It works but with flaws (I'm using Instruments and Zombies to debug it):
It seems to read every picture inside camera roll. So, first question: doesn't enumerateAssetsAtIndexes: only retrieve the specified images (atIndexes)? What's wrong with my code?
Adding to previous problem, memory becomes a problem when a lot of pictures are in camera roll. My app crashes if too many or, if it works, it seems retrieved images are NEVER deallocated, so the second or third time i call this code, it crashes anyway due to memory leaks. So second question: how do I force my code to deallocate everything after calling doTheJobWithImage: ?
I'm using xcode 4.6, ios 6 and ARC. Thanks in advance.
After few hours, I figured out that nothing is wrong with the code here. It is the right code to extract last picture in camera roll (if somebody ever needs it). The problem was inside doTheJobWithImage: , I solved it replacing
[self doTheJobWithImage:image];
and storing the resulting image somewhere else, and THEN performing doTheJobWithImage: after picture enumeration was done.. Saying this just in case somebody else is interested.

Resources