Usually when i want to read some image metadata in iOS i use imagePickerController to choose the image and Photos Framework and imagePickerController:didFinishPickingMediaWithInfo:
to get image info and extract the metadata like this
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info{
UIImagePickerControllerSourceType pickerType = picker.sourceType;
if(pickerType == UIImagePickerControllerSourceTypePhotoLibrary)
{
NSURL *url = [info objectForKey:UIImagePickerControllerReferenceURL];
PHFetchResult *fetchResult = [PHAsset fetchAssetsWithALAssetURLs:#[url,] options:nil];
PHAsset *asset = fetchResult.firstObject;
[self metaDataFromPhotoLibrary:asset];
[self dismissViewControllerAnimated:YES completion:NULL];
}
}
-(void)metaDataFromPhotoLibrary:(PHAsset*)asset{
// NSLog(#"Start metadata");
PHContentEditingInputRequestOptions *options = [[PHContentEditingInputRequestOptions alloc] init];
options.networkAccessAllowed = YES; //download asset metadata from iCloud if needed
[asset requestContentEditingInputWithOptions:options completionHandler:^(PHContentEditingInput *contentEditingInput, NSDictionary *info) {
CIImage *fullImage = [CIImage imageWithContentsOfURL:contentEditingInput.fullSizeImageURL];
NSDictionary *metadata = fullImage.properties;
NSMutableDictionary *imageMetadata = nil;
imageMetadata = [[NSMutableDictionary alloc] initWithDictionary:metadata];
NSString *dateString = metadata [#"{TIFF}"] [#"DateTime"];
NSString *latitude = metadata [#"{GPS}"][#"Latitude"];
NSString *longitude = metadata [#"{GPS}"][#"Longitude"];
//etc etc etc
}
But can't do the same thing from an Action Extension
in the extension code i use a code like this to get the selected image
- (void)viewDidLoad {
[super viewDidLoad];
// Get the item[s] we're handling from the extension context.
// For example, look for an image and place it into an image view.
// Replace this with something appropriate for the type[s] your extension supports.
BOOL imageFound = NO;
for (NSExtensionItem *item in self.extensionContext.inputItems) {
for (NSItemProvider *itemProvider in item.attachments) {
if ([itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeImage]) {
// This is an image. We'll load it, then place it in our image view.
__weak UIImageView *immagine = self.immagine;
[itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeImage options:nil completionHandler:^(UIImage *image, NSError *error) {
if(image) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[immagine setImage:image];
}];
}
}];
imageFound = YES;
break;
}
}
if (imageFound) {
// We only handle one image, so stop looking for more.
break;
}
}
}
Using UIImagePNGRepresentation or UIImageJPEGRepresentation i lost many metadata and i can read only a few data !
How can i get all image metadata from the image selected from action Extension ?
Thank you so mush
Note: i 've found some app in the AppsStore that read all metadata dictionary from extension , so there must be a solution for my problem !
Thank you again
Vanni
Related
I am having an interesting little problem using the uiimagepickercontroller and was wondering if anyone has any insight as to what might be happening. Users can take pictures with the camera or pick from the photo library until the cows come home as many times in a row as they like. My issue lies in allowing users to revert back to the original image that shipped with the app. Here is the flow:
Users go the the tableview which shows a thumbnail of the image.
Users navigate to the detail view which shows a larger view of the image.
Users can tap on the image in the detail view to bring up a custom alertcontroller with options to a) use the camera to take a picture, b) use a picture from their library, or c) revert back to the original image.
Users choose either option 'a' or option 'b' to either take a picture or use a picture from the photo library. IF they IMMEDIATELY change their mind about using one of those choices and want to just go back to using the original image, nothing happens! They can snap another picture or choose another image right away, but cannot revert back to the original image right away.
Reverting back to the original image DOES work perfectly when the app has been closed and then opened again. Sometimes it will work if you navigate around to other views within the app and then come back to the detail view where they just added their own image. By why the delay? I've searched around for two weeks but have not found anything resembling my problem or any solutions that help in any way (like reloading the headerview where image is sitting). Any thoughts?
Also I have figured out how to save the image to iCloud by using the documentation but cannot figure out how to retrieve them so there is no code for that. That is entirely different question. The same thing seems to occur even without that code.
Thanks for taking the time to look at this!
Here is some code:
-(void)bookImageTapped:(UIGestureRecognizer *)gesture
{
URBAlertView *changeImageAlertView = [[URBAlertView alloc] initWithTitle:#"Add A New Book Cover Image" message:nil cancelButtonTitle:#"Cancel" otherButtonTitles:#"Use Camera", #"Open Gallery", #"Use Original Photo", nil];
[changeImageAlertView setHandlerBlock:^(NSInteger buttonIndex, URBAlertView *alertView) {
[self checkPermission];
if (PHAuthorizationStatusAuthorized)
{
if(buttonIndex == 0)
{
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
UIImagePickerController *pickerController = [[UIImagePickerController alloc] init];
pickerController.sourceType = UIImagePickerControllerSourceTypeCamera;
pickerController.delegate = self;
pickerController.allowsEditing = NO;
pickerController.mediaTypes = [UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypeCamera];
[self presentViewController:pickerController animated:YES completion:nil];
}];
[alertView hide];
}
else
{
NSLog(#"Camera not available");
[alertView hide];
}
}
else if (buttonIndex == 1)
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
UIImagePickerController *pickerController = [[UIImagePickerController alloc] init];
pickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
pickerController.delegate = self;
pickerController.allowsEditing = NO;
pickerController.mediaTypes = [UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypePhotoLibrary];
[self presentViewController:pickerController animated:YES completion:nil];
}];
[alertView hide];
}
else if (buttonIndex == 2)
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self restoreOriginalPhoto];
}];
[alertView hide];
}
else
{
NSLog(#"button 2 cancel");
[alertView hide];
}
}
}];
[changeImageAlertView show];
}
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(nonnull NSDictionary<NSString *,id> *)info
{
[picker dismissViewControllerAnimated:YES completion:nil];
_book.largeBookImage = [info objectForKey:UIImagePickerControllerOriginalImage];
_book.largeBookImage = [self scaleImage:_book.largeBookImage toSize:CGSizeMake(120, 168)];
_bookImageView.image = _book.largeBookImage;
_book.wasNewImageAdded = YES;
_book.originalImageUsed = NO;
NSString * documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
[self saveImage:_book.largeBookImage withFileName:_book.bookImageID ofType:#"jpg" inDirectory:documentsDirectory];
}
-(void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
[picker dismissViewControllerAnimated:YES completion:nil];
}
-(void)saveImage:(UIImage *)image withFileName:(NSString *)imageName ofType:(NSString *)extension inDirectory:(NSString *)directoryPath
{
if ([[extension lowercaseString] isEqualToString:#"png"])
{
[UIImagePNGRepresentation(image) writeToFile:[directoryPath stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.%#", imageName, #"png"]] options:NSAtomicWrite error:nil];
//Create a URL to the local file
NSURL *resourceURL = [NSURL fileURLWithPath:[directoryPath stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.%#", imageName, #"png"]]];
if (resourceURL)
{
CKAsset *asset = [[CKAsset alloc] initWithFileURL:resourceURL];
//create a record object
CKRecord *bookCover = [[CKRecord alloc] initWithRecordType:#"Bookcover"];
//set the record's fields
bookCover[#"title"] = _book.title;
bookCover[#"bookImage"] = asset;
/* TO SAVE A RECORD */
//get the public database
CKContainer *appContainer = [CKContainer defaultContainer];
CKDatabase *publicDatabase = [appContainer publicCloudDatabase];
[publicDatabase saveRecord:bookCover completionHandler:^(CKRecord *bookCover, NSError *error) {
if (error)
{
//insert error handling
return;
}
//insert succesfully saved record code
NSLog(#"png record saved after using picker!");
}];
}
}
else if ([[extension lowercaseString] isEqualToString:#"jpg"] || [[extension lowercaseString] isEqualToString:#"jpeg"])
{
[UIImageJPEGRepresentation(image, 1.0) writeToFile:[directoryPath stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.%#", imageName, #"jpg"]] options:NSAtomicWrite error:nil];
//Create a URL to the local file
NSURL *resourceURL = [NSURL fileURLWithPath:[directoryPath stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.%#", imageName, #"jpg"]]];
if (resourceURL)
{
CKAsset *asset = [[CKAsset alloc] initWithFileURL:resourceURL];
//create a record object
CKRecord *bookCover = [[CKRecord alloc] initWithRecordType:#"Bookcover"];
//set the record's fields
bookCover[#"title"] = _book.title;
bookCover[#"bookImage"] = asset;
/* TO SAVE A RECORD */
//get the public database
CKContainer *appContainer = [CKContainer defaultContainer];
CKDatabase *publicDatabase = [appContainer publicCloudDatabase];
[publicDatabase saveRecord:bookCover completionHandler:^(CKRecord *bookCover, NSError *error) {
if (error)
{
//insert error handling
return;
}
//insert succesfully saved record code
NSLog(#"jpg record saved after using picker!");
}];
}
}
else
{
NSLog(#"Image Save Failed\nExtension: (%#) is not recognized, use (PNG/JPG)", extension);
}
}
- (UIImage *) scaleImage:(UIImage*)image toSize:(CGSize)newSize
{
UIGraphicsBeginImageContextWithOptions(newSize, NO, 0.0);
[image drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
-(void)restoreOriginalPhoto
{
NSLog(#"restore photo called");
_book.originalImageUsed = YES;
_book.wasNewImageAdded = NO;
_bookImageView.image = _book.largeBookImage;
NSString * documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
[self saveImage:_book.largeBookImage withFileName:_book.bookImageID ofType:#"jpg" inDirectory:documentsDirectory];
}
Here is the headerview with the imageview:
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
_headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, 26)];
_headerView.backgroundColor = [UIColor colorWithRed:8/255.0 green:46/255.0 blue:46/255.0 alpha:0.8];
if (section == 0)
{
_headerView.backgroundColor = [UIColor whiteColor];
_bookImageView = [[UIImageView alloc] initWithFrame:CGRectMake((tableView.frame.size.width - 120)/2, 6, 120, 168)];
_bookImageView.contentMode = UIViewContentModeScaleAspectFit;
if (_book.wasNewImageAdded)
{
NSString * documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
UIImage * image = [self loadImageWithFileName:_book.bookImageID ofType:#"jpg" inDirectory:documentsDirectory];
_bookImageView.image = image;
}
else
{
_bookImageView.image = _book.largeBookImage;
}
if(_book.originalImageUsed)
{
NSString * documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
UIImage * image = [self loadImageWithFileName:_book.bookImageID ofType:#"jpg" inDirectory:documentsDirectory];
_bookImageView.image = image;
}
UITapGestureRecognizer *bookImageTouched = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(bookImageTapped:)];
bookImageTouched.numberOfTapsRequired = 1;
[_bookImageView addGestureRecognizer:bookImageTouched];
_bookImageView.userInteractionEnabled = YES;
[_headerView addSubview:_bookImageView];
}
I finally figured it out! It seems that I was confusing xcode with my property names. The code ended up much simpler in the end.
In didFinishPickingMediaWithInfo I created a UIImage and then set it to the bookImageView.image. Later, when I wanted to be able to update the image back to the original image, then I could call the bundle asset, _book.largeBookImage. Voila! The image was able to update immediately.
The most pertinent code is posted below.
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(nonnull NSDictionary<NSString *,id> *)info
{
[picker dismissViewControllerAnimated:YES completion:nil];
_chosenImage = [[UIImage alloc] init];
_chosenImage = [info objectForKey:UIImagePickerControllerOriginalImage];
_bookImageView.image = _chosenImage;
_book.wasNewImageAdded = YES;
_book.originalImageUsed = NO;
NSString * documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
[self saveImage:_chosenImage withFileName:_book.bookImageID ofType:#"jpg" inDirectory:documentsDirectory];
}
-(void)saveImage:(UIImage *)image withFileName:(NSString *)imageName ofType:(NSString *)extension inDirectory:(NSString *)directoryPath
{
if ([[extension lowercaseString] isEqualToString:#"png"])
{
[UIImagePNGRepresentation(image) writeToFile:[directoryPath stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.%#", imageName, #"png"]] options:NSAtomicWrite error:nil];
[self.tableView reloadData];
}
else if ([[extension lowercaseString] isEqualToString:#"jpg"] || [[extension lowercaseString] isEqualToString:#"jpeg"])
{
[UIImageJPEGRepresentation(image, 1.0) writeToFile:[directoryPath stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.%#", imageName, #"jpg"]] options:NSAtomicWrite error:nil];
[self.tableView reloadData];
}
else
{
//NSLog(#"Image Save Failed\nExtension: (%#) is not recognized, use (PNG/JPG)", extension);
}
}
-(void)restoreOriginalPhoto
{
_book.originalImageUsed = YES;
_book.wasNewImageAdded = NO;
_bookImageView.image = _book.largeBookImage;
_backgroundImage.image = _book.largeBookImage;
}
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
if (section == 0)
{
_bookImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 120, 168)];
_bookImageView.contentMode = UIViewContentModeScaleAspectFit;
_bookImageView.clipsToBounds = YES;
_bookImageView.layer.cornerRadius = 10.0f;
if (_book.wasNewImageAdded)
{
NSString * documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
UIImage * image = [self loadImageWithFileName:_book.bookImageID ofType:#"jpg" inDirectory:documentsDirectory];
_bookImageView.image = image;
}
else
{
_bookImageView.image = _book.largeBookImage;
}
if(_book.originalImageUsed)
{
_bookImageView.image = _book.largeBookImage;
}
}
}
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if(_book.originalImageUsed)
{
_bookImageView.image = _book.largeBookImage;
}
[self.tableView reloadData];
[self.tableView setContentOffset:CGPointZero animated:NO];
}
I have tried a lot of solutions provided on other questions which were same as mine but nothing could help me much.
Let me tell you what am I doing. I have a collection view. In that I will display some images which will be captured by camera. I am capturing multiple pictures at a time. All the pictures which I have taken, the address of those images will first save into database and then those images will be displayed in collection view.
Now what happens, when I click 40-50 images at a time, the app is crashed and xcode displays a message something like "app is terminating due to memory pressure". Also I am getting too many memory warnings in logs but actually I was neglecting them.
First I am writing code for taking multiple pictures-
- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary *)info
{
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
//Get Image URL from Library
NSString *mediaType = [info objectForKey:UIImagePickerControllerMediaType];
NSURL *urlPath = [info valueForKey:UIImagePickerControllerReferenceURL];
if (segmentControl.selectedSegmentIndex != 1) {
[picker dismissViewControllerAnimated:YES completion:nil];
}
if (segmentControl.selectedSegmentIndex == 2) {
[self insertPicToDB:urlPath];
}else{
__block NSURL *url;
if ([mediaType isEqualToString:(NSString *)kUTTypeImage]) {
UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
// Request to save the image to camera roll
[library writeImageToSavedPhotosAlbum:[image CGImage] orientation:(ALAssetOrientation)[image imageOrientation] completionBlock:^(NSURL *assetURL, NSError *error){
if (error) {
} else {
url = assetURL;
[self insertPicToDB:url];
}
}];
}
}
}
}
After while taking each picture, I am saving image url in db and then at the same time trying to reload the collection view as well.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
static NSString *identifier = #"Cell";
collectionCell = (CollectionCell *)[_collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
collectionCell.imageView = (UIImageView*)[collectionCell viewWithTag:100];
collectionCell.imageView.image = [UIImage imageNamed:#"placeholder.png"];
NSString *fileURL = [[recipeImages[indexPath.section] objectAtIndex:indexPath.item] objectForKey:#"FileUrl"];
collectionCell.imagURL = fileURL;
if ([fileURL hasPrefix:#"assets-library"]) {
[self getImageFromURL:[NSURL URLWithString:fileURL] :indexPath];
}else{
fileURL = [NSString stringWithFormat:#"%#/uploads/thumbnail/%#",[[HttpClient sharedInstance]getBaseURLString],[[fileURL componentsSeparatedByString:#"\\"]lastObject]];
[collectionCell.imageView setImageWithURL:[NSURL URLWithString:fileURL] placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
}
return collectionCell;
}
So the condition is, I will keep clicking the pictures and the pictures will be saving in background.
And the definition of method "getImageFromURL" is-
-(void)getImageFromURL:(NSURL*)yourUrl :(NSIndexPath*)indexPath{
ALAssetsLibraryAssetForURLResultBlock resultblock = ^(ALAsset *myasset)
{
ALAssetRepresentation *rep = [myasset defaultRepresentation];
#autoreleasepool {
CGImageRef iref = [rep fullScreenImage];
if (iref) {
UIImage *image = [UIImage imageWithCGImage:iref];
dispatch_async(dispatch_get_main_queue(), ^{
collectionCell = (CollectionCell*)[_collectionView cellForItemAtIndexPath:indexPath];
if (collectionCell) {
NSData *imageData = UIImageJPEGRepresentation(image, 0.1);
UIImage *compressedImage = [UIImage imageWithData:imageData];
collectionCell.imageView.image = compressedImage;
}else{
collectionCell.imageView.image = nil;
}
[collectionCell.imageView setNeedsDisplay];
[collectionCell setNeedsDisplay];
});
iref = nil;
}
}
};
ALAssetsLibraryAccessFailureBlock failureblock = ^(NSError *myerror)
{
NSLog(#"Can't get image - %#",[myerror localizedDescription]);
};
ALAssetsLibrary* assetslibrary = [[ALAssetsLibrary alloc] init];
[assetslibrary assetForURL:yourUrl
resultBlock:resultblock
failureBlock:failureblock];
}
I am also trying to compress the images while fetching in collection view. So I don't think that it is crashing because of collection view. What can be the reason? Is it because of I am using ALAssetsLibrary or something else?
I was debugging it in iPhone 4S with iOS version 7.1.1.
Thanks in advance.
I can get the .MOV file with the Image Picker, but how can I find the location it was taken at and the time it was taken?
My image picker:
- (void) imagePickerController: (UIImagePickerController *) picker
didFinishPickingMediaWithInfo: (NSDictionary *) info {
NSString *mediaType = [info objectForKey: UIImagePickerControllerMediaType];
[self dismissViewControllerAnimated:NO completion:nil];
// Handle a movie capture
if (CFStringCompare ((__bridge_retained CFStringRef)mediaType, kUTTypeMovie, 0)
== kCFCompareEqualTo) {
NSString *movieFile = [[info objectForKey:
UIImagePickerControllerMediaURL] path];
NSURL *movieURL = [NSURL fileURLWithPath:movieFile];
NSLog(#"Movie File %#",movieURL);
[self dismissViewControllerAnimated:YES completion:nil];
}
}
I don't know about the location it was taken but you can use File Attribute Keys to get the NSFileCreationDate using attributesOfItemAtPath:error:. There is a convenience method on NSDictionary fileCreationDate
NSError *error;
NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:movieFile error:&error];
if (!attributes) {
// Use the error
}
NSDate *createdDate = [attributes fileCreationDate];
If you want to access the meta-data on the MOV file you can have a look at the EXIF data, I don't know if there is a iOS library for this though.
I am ultimately trying to convert an array of photos that are in a dictionary in url rep form to base64 to be sent over json.
Here is the dictionary code and log of it:
NSDictionary *dict = [self.form dictionaryWithValuesForKeys:keys];
NSLog(#"dict::%#",dict);
NSLog:
dict::{
boardLodgingFurnished = "<null>";
caption = "<null>";
cars = "";
photos = (
{
caption = "";
urlRep = "assets-library://asset/asset.JPG?id=CE8A426B-3B59-4172-8761-CC477F3BB3EE&ext=JPG";
},
{
caption = "";
urlRep = "assets-library://asset/asset.JPG?id=F4B68A42-1CA0-4880-9FB5-177CB091A28C&ext=JPG";
}
);
yearsAtLocation = "";
yearsInTheBusiness = "";
}
So for each photo in the dictionary I would like to take the urlRep and convert that to a base64 string and replace the urlRep with it in the dictionary.
What I have right now..not sure if I am going in the right direction:
for (id imageURL in [dict objectForKey:#"photos"])
{
ALAssetsLibrary *library = [ALAssetsLibrary new];
ALAsset *ourAsset = [self assetForURL:imageURL withLibrary:library];
/* Check out ALAssets */
NSLog(#"%#", ourAsset);
ALAssetRepresentation *representation = [ourAsset defaultRepresentation];
CGImageRef imageRef = [representation fullResolutionImage];
//TODO: Deal with JPG or PNG
NSData *imageData = UIImageJPEGRepresentation([UIImage imageWithCGImage:imageRef], 0.1);
NSLog(#"imagedata??%#", [imageData base64EncodedString]);
//need to know how to add this back to dict
}
the below method is called from above but crashes on the while loop with
-[__NSDictionaryI scheme]: unrecognized selector sent to instance 0x166dd090
2014-01-03 10:57:27.361 Inspection App[2728:60b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSDictionaryI scheme]: unrecognized selector sent to instance 0x166dd090'
method
- (ALAsset *)assetForURL:(NSURL *)url withLibrary:(ALAssetsLibrary *)assetsLibrary {
__block ALAsset *result = nil;
__block NSError *assetError = nil;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[assetsLibrary assetForURL:url resultBlock:^(ALAsset *asset) {
result = asset;
dispatch_semaphore_signal(sema);
} failureBlock:^(NSError *error) {
assetError = error;
dispatch_semaphore_signal(sema);
}];
if ([NSThread isMainThread]) {
while (!result && !assetError) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
else {
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}
return result;
}
edit:
if (photoUrls.count) {
for (id photos in photoUrls){
NSString *urlString = photos;
[self base64ImageAtUrlString:urlString result:^(NSString *base64) {
NSLog(#"imagedata??%#", base64);
}];
}
}
else {
NSLog(#"where are my urls?");
}
NSMutableDictionary *jsonWithPhotos = [dict mutableCopy];
[jsonWithPhotos setObject:convertedImages forKey:#"photo64"];
NSLog(#"jjson photos::%#", jsonWithPhotos);
updated method
- (void)base64ImageAtUrlString:(NSString *)urlString result:(void (^)(NSString *))completion {
NSURL *url = [NSURL URLWithString:urlString];
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library assetForURL:url resultBlock:^(ALAsset *asset) {
// borrowing your code, here... didn't check it....
ALAssetRepresentation *representation = [asset defaultRepresentation];
CGImageRef imageRef = [representation fullResolutionImage];
//TODO: Deal with JPG or PNG
NSData *imageData = UIImageJPEGRepresentation([UIImage imageWithCGImage:imageRef], 0.1);
NSString *base64 = [imageData base64EncodedString];
completion(base64);
[convertedImages addObject:base64];
// NSLog(#"converted::%#",convertedImages);
} failureBlock:^(NSError *error) {
NSLog(#"that didn't work %#", error);
}];
}
When i log jsonWithPhotos the object photo64 is just a blank array
The crash is due to a false assumption in the code about the dictionary. Given the posted description of the dictionary parsed as json, you'd need to get the urls like this:
// collect the photo urls in an array
NSMutableArray *photoUrls = [NSMutableArray array];
// photos is an array of dictionaries in the dictionary
NSArray *photos = dict[#"photos"];
for (NSDictionary *photo in photos) {
// photo is a dictionary containing a "caption" and a "urlRep"
[photoUrls addObject:photo[#"urlRep"]];
}
Now you can proceed with a method whose job is just the conversion. Your question might contain a lot more questions about how to do this. I'd recommend starting simple. See if you can do one conversion. Test it by writing the reverse, from base64 back to an image.
Edit 0: Without deeply checking it, I'd restructure your encoding attempt to look like this:
- (void)base64ImageAtUrlString:(NSString *)urlString result:(void (^)(NSString *))completion {
NSURL *url = [NSURL URLWithString:urlString];
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library assetForURL:url resultBlock:^(ALAsset *asset) {
// borrowing your code, here... didn't check it....
ALAssetRepresentation *representation = [asset defaultRepresentation];
CGImageRef imageRef = [representation fullResolutionImage];
//TODO: Deal with JPG or PNG
NSData *imageData = UIImageJPEGRepresentation([UIImage imageWithCGImage:imageRef], 0.1);
NSString *base64 = [imageData base64EncodedString];
completion(base64);
} failureBlock:^(NSError *error) {
NSLog(#"that didn't work %#", error);
}];
}
Call it like this:
if (photoUrls.count) {
NSString *urlString = photoUrls[0];
[self base64ImageAtUrlString:urlString result:^(NSString *base64) {
NSLog(#"imagedata??%#", base64);
}];
} else {
NSLog(#"where are my urls?");
}
Once it's working, see if you can reverse it, making an image out of the base64 data. Finally, once all that's working, you can deal with potential memory issues. My advice there is to consider encoding one at a time, post one at a time to the server and release everything in between.
Edit 1 - Per followup question, if you want to replace all of the urls in the url array with base64 encodings, it might go something like this (remember that this might use a lot of memory):
- (void)base64ImagesAtUrls:(NSMutableArray *)urls result:(void (^)(void))completion {
__block NSInteger completed = 0; // this is how we'll know that we're done
// this approach doesn't depend on the asset library retrievals completing
// sequentially, even though they probably will
for (int i=0; i<urls.count; i++) {
NSString *urlString = urls[i];
[self base64ImageAtUrlString:urlString result:^(NSString *base64) {
[urls replaceObjectAtIndex:i withObject:base64];
if (++completed == urls.count) completion();
}];
}
}
So I'm building an app that the user takes pictures of themselves, it saves them to the camera roll, and I'm saving references to the asset URLs to display them in the app. At first this model seemed to work fine, but as I took more and more pictures it started receiving memory warnings and eventually crashed. Is there a better way to approach this?
This is how I load up the saved photos at the launch of the app (which freezes the app for up to 10 seconds depending on how many are being loaded):
- (void) loadPhotosArray
{
_photos = [[NSMutableArray alloc] init];
NSData* data = [[NSUserDefaults standardUserDefaults] objectForKey: #"savedImages"];
if (data)
{
NSArray* storedUrls = [[NSArray alloc] initWithArray: [NSKeyedUnarchiver unarchiveObjectWithData: data]];
// reverse array
NSArray* urls = [[storedUrls reverseObjectEnumerator] allObjects];
for (NSURL* assetUrl in urls)
{
// Block to handle image handling success
ALAssetsLibraryAssetForURLResultBlock resultblock = ^(ALAsset *myasset)
{
ALAssetRepresentation *rep = [myasset defaultRepresentation];
CGImageRef iref = [rep fullResolutionImage];
if (iref) {
UIImage* tempImage = [UIImage imageWithCGImage:iref];
UIImage* image = [[UIImage alloc] initWithCGImage: tempImage.CGImage scale: 1.0 orientation: UIImageOrientationRight];
// Set image in imageView
[_photos addObject: image];
[[NSNotificationCenter defaultCenter] postNotificationName: #"PhotosChanged" object: self];
}
};
// Handles failure of getting image
ALAssetsLibraryAccessFailureBlock failureblock = ^(NSError *myerror)
{
NSLog(#"Can't get image - %#",[myerror localizedDescription]);
};
// Load image then call appropriate block
ALAssetsLibrary* assetslibrary = [[ALAssetsLibrary alloc] init];
[assetslibrary assetForURL: assetUrl
resultBlock: resultblock
failureBlock: failureblock];
}
}
else
{
NSLog(#"Photo storage is empty");
}
}
And saving photos:
- (void) addImageToPhotos: (UIImage*)image
{
// Store image at front of array
NSMutableArray* temp = [[NSMutableArray alloc] initWithObjects: image, nil];
// load rest of images onto temp array
for (UIImage* image in _photos)
{
[temp addObject: image];
}
_photos = nil;
_photos = [[NSMutableArray alloc] initWithArray: temp];
// [self.photos addObject: image];
[[NSNotificationCenter defaultCenter] postNotificationName: #"PhotosChanged" object: self.photos];
// save to cache
ALAssetsLibrary* library = [[ALAssetsLibrary alloc] init];
[library saveImage: image toAlbum: #kAlbumeName withCompletionBlock:^(NSError *error) {
if (error)
{
NSLog(#"Error saving");
}
}];
}
I think have 2 methods to optimize this problem.
U should just save image name string instead of saving UIImage object, then when need to display the image, use pagination to display image according to saved image name string.
U should use multi-thread to deal with this long time task, recommend u to use gcd to load image name string.