I know how to let user select an image from UIImagePickerController, but I don't want that.
I just want to have NSArray of images stored in the phone, but I don't want to involve user (to select a one and then have that image...),rather, I have created my own custom Image selector controller and want to have source as the gallary.
You can easily do that using the AVFoundation and AssetsLibrary framework. Here is the code to access all the photos:
-(void)addPhoto:(ALAssetRepresentation *)asset
{
//NSLog(#"Adding photo!");
[photos addObject:asset];
}
-(void)loadPhotos
{
photos = [[NSMutableArray alloc] init];
library = [[ALAssetsLibrary alloc] init];
// Enumerate just the photos and videos group by using ALAssetsGroupSavedPhotos.
if([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)
{
[library enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *group, BOOL *stop)
{
// Within the group enumeration block, filter if necessary
[group setAssetsFilter:[ALAssetsFilter allPhotos]];
[group enumerateAssetsUsingBlock:^(ALAsset *alAsset, NSUInteger index, BOOL *innerStop)
{
// The end of the enumeration is signaled by asset == nil.
if (alAsset)
{
ALAssetRepresentation *representation = [alAsset defaultRepresentation];
[self addPhoto:representation];
}
else
{
NSLog(#"Done! Count = %d", photos.count);
//Do something awesome
}
}];
}
failureBlock: ^(NSError *error) {
// Typically you should handle an error more gracefully than this.
NSLog(#"No groups");
}];
}
}
Related
I have a problem when I use the below code to load the photo album out from the iphone. Although I loaded perfectly, the first photo appeared is the oldest photo. Is it possible to rearrange the order so that I can get the latest photo loaded first followed by time and oldest at the back?
- (IBAction)imageFromAlbum:(id)sender
{
imagePicker = [[UIImagePickerController alloc] init];
imagePicker.delegate = self;
imagePicker.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;
[self presentViewController:imagePicker animated:YES completion:nil];
}
To achieve what you want, you will have to create custom photo picker of your own using asset library framework. depending on the ios version you are using you can use UICollectionViewController (ios6 onwards) or will have to create your own.
To get the all the photos/assets in the saved photos album, execute this method and then sort the objects using their creation date in descending order using property ALAssetPropertyDate
thisVC.assetArray will be the datasource for your custom table or collection view controller. These two methods are asynchronous, so you will need to refresh the tableView or collectionView once the datasource is complete
__block YourViewController *thisVC = self;
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
if (group) {
[self enumerateAssetForGroup:group forFilter:[ALAssetsFilter allPhotos] withCompletionBlock:^(id object) {
thisVC.assetArray = object;
[thisVC.assetArray sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
NSDate *date1 = [obj1 valueForProperty:ALAssetPropertyDate];
NSDate *date2 = [obj2 valueForProperty:ALAssetPropertyDate];
return ([date1 compare:date2] == NSOrderedAscending ? NSOrderedDescending : NSOrderedAscending);
}];
//in case of table
[thisVC.tableView reloadData];
//in case of collection view
//reload collection view controller data
}];
}
} failureBlock:nil];
- (void)enumerateAssetForGroup:(ALAssetsGroup*)group forFilter:(ALAssetsFilter*)filter withCompletionBlock:(ALAssetsEnumeration)enumerationCompletionBlock {
[group setAssetsFilter:filter];
__block NSInteger assetsCount = [group numberOfAssets];
__block NSMutableArray *assetArray = [[NSMutableArray alloc] init];
[group enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
if (result) {
[assetArray addObject:result];
if (*stop) {
enumerationCompletionBlock(assetArray);
[assetArray release];
}
}
else if (assetsCount == 0) {
enumerationCompletionBlock(nil);
}
}];
}
this part written in the first method will sort your array in descending order,
[thisVC.assetArray sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
NSDate *date1 = [obj1 valueForProperty:ALAssetPropertyDate];
NSDate *date2 = [obj2 valueForProperty:ALAssetPropertyDate];
return ([date1 compare:date2] == NSOrderedAscending ? NSOrderedDescending : NSOrderedAscending);
}];
Try this out if you really want to :)
As shown here, you can get the actual filename of the image from the UIImagePicker. Since you seem to be able to access them in date order, a reverse enumeration of this result should sort your problem. Hope that helps...
I'm trying to pick out some camera roll meta data. When I enumerate through the assets, I cannot seem to retrieve any information and get an empty array. Is there a step I'm missing?
My code:
assets = [[NSMutableArray array] init];
void (^assetEnumerator)(ALAsset *, NSUInteger, BOOL *) = ^(ALAsset *asset, NSUInteger index, BOOL *stop) {
if(asset != NULL) {
[assets addObject:asset];
dispatch_async(dispatch_get_main_queue(), ^{
});
}
};
void (^assetGroupEnumerator)(ALAssetsGroup *, BOOL *) = ^(ALAssetsGroup *group, BOOL *stop) {
if(group != nil) {
[group enumerateAssetsUsingBlock:assetEnumerator];
}
};
library = [[ALAssetsLibrary alloc] init];
[library enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos
usingBlock:assetGroupEnumerator
failureBlock: ^(NSError *error) {
NSLog(#"Failed.");
}];
NSLog(#"%#", assets); //prints an empty array
Midhun MP is right that you are not waiting for the asynchronous enumeration to complete. In this case, you have asynchronous blocks calling other asynchronous blocks, so it is not simple to know when all enumeration is done.
If you would like to know when this is done, and end up with an array that contains all of the enumerated assets, you could use dispatch_groups. Here is one way you could do that (I've included multiple ALAssetGroup types to show that this can work with multiple albums):
dispatch_group_t loadingGroup = dispatch_group_create();
NSMutableArray * assets = [[NSMutableArray array] init];
NSMutableArray * albums = [[NSMutableArray array] init];
void (^assetEnumerator)(ALAsset *, NSUInteger, BOOL *) = ^(ALAsset *asset, NSUInteger index, BOOL *stop) {
if(index != NSNotFound) {
[assets addObject:asset];
dispatch_async(dispatch_get_main_queue(), ^{ });
} else {
dispatch_group_leave(loadingGroup);
}
};
void (^assetGroupEnumerator)(ALAssetsGroup *, BOOL *) = ^(ALAssetsGroup *group, BOOL *stop) {
if(group != nil) {
[albums addObject: group];
} else {
NSLog(#"Found %d albums", [albums count]);
// album loading is done
for (ALAssetsGroup * album in albums) {
dispatch_group_enter(loadingGroup);
[album enumerateAssetsUsingBlock: assetEnumerator];
}
dispatch_group_notify(loadingGroup, dispatch_get_main_queue(), ^{
NSLog(#"DONE: ALAsset array contains %d elements", [assets count]);
});
}
};
ALAssetsLibrary * library = [[ALAssetsLibrary alloc] init];
[library enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos | ALAssetsGroupAlbum
usingBlock:assetGroupEnumerator
failureBlock: ^(NSError *error) {
NSLog(#"Failed.");
}];
(In this example, it is safe to have various blocks adding to assets and albums because the enumeration is all happening on the main thread.)
If you are running this on iOS 6, it may be that the user has denied access for you app to access the asset library. If this is the case, the failureBlock is called.
Also note that the usingBlock is called asynchronously so your attempt to log assets is premature. You should move the NSLog statement into the end of the enumeration block.
From the docs for enumerateGroupsWithTypes:usingBlock:failureBlock:
The results are passed one by one to the caller by executing the enumeration block.
This method is asynchronous. When groups are enumerated, the user may be asked to confirm the application's access to the data; the method, though, returns immediately. You should perform whatever work you want with the assets in enumerationBlock.
If the user denies access to the application, or if no application is allowed to access the data, the failureBlock is called.
Your NSLog will always display an empty array because the NSLog statement will work before completing the asynchronous enumeration block.
Solution:
First check that your photolibrary is not empty.
Then add NSLog in:
void (^assetEnumerator)(ALAsset *, NSUInteger, BOOL *) = ^(ALAsset *asset, NSUInteger index, BOOL *stop) {
if(asset != NULL) {
[assets addObject:asset];
NSLog(#"Asset : %#", asset);
dispatch_async(dispatch_get_main_queue(), ^{
});
}
};
I am experimenting with the Social Framework.
I was wondering if there is any possible way to attach the last photo taken and currently saved in the camera roll using this implementation from my app:
- (IBAction)shareByActivity:(id)sender {
NSArray *activityItems;
if (self.sharingImage != nil) {
activityItems = #[self.sharingImage, self.sharingText, self.addURL];
} else {
activityItems = #[self.sharingText, self.addURL];
}
UIActivityViewController *activityController =
[[UIActivityViewController alloc] initWithActivityItems:activityItems
applicationActivities:nil];
[self presentViewController:activityController
animated:YES completion:nil];
}
If so how do I modify the shareImage name in this specific portion of my -(void)viewDidLoad ?
self.sharingImage = [UIImage imageNamed:#"notSureWhatToPutHere"];
Everything works well, the social panel opens and has all the needed service. My only request is to find out how to call the latest image from the camera roll.
I am not sure if I need the image real name or if there is any other way to achieve what i would like: a post with a picture attached (most recent picture in camera roll).
Any help is appreciated.
You can pretty painlessly get the last image in the camera roll using the asset library framework. Give this a try:
#import <AssetsLibrary/AssetsLibrary.h>
Then to get the image:
ALAssetsLibrary *cameraRoll = [[ALAssetsLibrary alloc] init];
[cameraRoll enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *images, BOOL *stop) {
[images setAssetsFilter:[ALAssetsFilter allPhotos]];
[images enumerateAssetsAtIndexes:[NSIndexSet indexSetWithIndex:[images numberOfAssets] - 1] options:0 usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop){
if (result) {
ALAssetRepresentation *rep = [result defaultRepresentation];
self.sharingImage = [UIImage imageWithCGImage:[rep fullScreenImage]];
}
}];
}
failureBlock: ^(NSError *error) {
NSLog(#"An error occured: %#",error);
}];
Thanks for the great help NSPostWhenIdle!
I was able to resolve the issue of getting "newest photos" from camera roll by moving my ALAssettsLibrary chuck of code away from viewDidLoad and created a refreshAlbum
- (void) refreshAlbum
{
ALAssetsLibrary *cameraRoll = [[ALAssetsLibrary alloc] init];
[cameraRoll enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *images, BOOL *stop) {
[images setAssetsFilter:[ALAssetsFilter allPhotos]];
[images enumerateAssetsAtIndexes:[NSIndexSet indexSetWithIndex:[images numberOfAssets] - 1] options:0 usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop)
{
if (result) {
ALAssetRepresentation *rep = [result defaultRepresentation];
self.sharingImage = [UIImage imageWithCGImage:[rep fullScreenImage]];
}
}];
}
failureBlock: ^(NSError *error) {
NSLog(#"An error occured: %#",error);
}];
}
Then I am calling the newly created refreshAlbum from the same button that enable the shareByActivity
- (IBAction)refreshButton:(id)sender {
[self refreshAlbum];
}
This work particularly well as my app does not have any picture to share as it starts up.
The first time that imageShare become available is after activating the shareByActivity button that now carries the refreshButton function as well!
Thanks again, this really work.
When I am fetching photos using the ALAssetLibrary, for some images, the AssetRepresentation.size comes zero, which does not make the image, on my ImageView.
Here is the code:
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library enumerateGroupsWithTypes:ALAssetsGroupAlbum usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
if ([[group valueForProperty:ALAssetsGroupPropertyName] isEqual:self.groupName]) {
[group enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop){
//Get the asset type
NSString *assetType = [result valueForProperty:ALAssetPropertyType];
if ([assetType isEqualToString:ALAssetTypePhoto]) {
NSLog(#"Photo Asset");
}
//Get URLs for the assets
NSDictionary *assetURLs = [result valueForProperty:ALAssetPropertyURLs];
NSUInteger assetCounter = 0;
for (NSString *assetURLKey in assetURLs) {
assetCounter++;
}
//Get the asset's representation object
ALAssetRepresentation *assetRepresentation = [result defaultRepresentation];
//From this asset representation, we take out data for image and show it using imageview.
dispatch_async(dispatch_get_main_queue(), ^(void){
CGImageRef imgRef = [assetRepresentation fullResolutionImage];
//Img Construction
UIImage *image = [[[UIImage alloc] initWithCGImage:imgRef] autorelease];
NSLog(#"before %#:::%lld", [image description], [assetRepresentation size]); //Prints '0' for size
if((image != nil)&& [assetRepresentation size] != 0){
//display in image view
}
else{
// NSLog(#"Failed to load the image.");
}
});
}];
}
}failureBlock:^(NSError *error){
NSLog(#"Error retrieving photos: %#", error);
}];
[library release];
Please Help. What am I doing wrong here? and how should I get the image?
Joe Smith is right! You release the library too soon. The moment the AssetLibrary is released, all the asset object will be gone with it. And Because these enumeration is block codes so the release of AssetLibrary will be executed somewhere during the reading process.
I suggest that you create your assetLibrary in the app delegate to keep it alive and only reset it when you receive change notification ALAssetsLibraryChangedNotification from the ALAssetLibrary
I think you released the assets library too soon.
Allow me to preface this by saying this is my first time using the ALAssetsLibrary. I need to access the most recent photo in the user's saved photo gallery. It seems that to do this, I have to create an ALAssetsLibrary instance and iterate over every item in the user's gallery before selecting the last image. This is always worst-case scenario. Is there a faster/better way to approach this problem?
You don't have to enumerate all the photos in the user's gallery.
The ALAssetsGroup class has a method - (void)enumerateAssetsAtIndexes:(NSIndexSet *)indexSet options:(NSEnumerationOptions)options usingBlock:(ALAssetsGroupEnumerationResultsBlock)enumerationBlock which you can use to indicate which assets you want to enumerate.
In your case it's only the last one so set indexSet to [NSIndexSet indexSetWithIndexesInRange:NSMakeRange([group numberOfAssets]-1, [group numberOfAssets]) where group is your ALAssetsGroup.
As #mithuntnt mentioned, you can get the ALAssetsGroup for the photo library using [[assetsLibrary] enumerateGroupsWithTypes:ALAssetsGroupAlbum usingBlock:^(ALAssetsGroup *group, BOOL *stop)
What about this:
[group enumerateAssetsWithOptions:NSEnumerationReverse usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
if (result) {
*stop = YES;
//...
}
}];
http://developer.apple.com/library/ios/#documentation/AssetsLibrary/Reference/ALAssetsLibrary_Class/Reference/Reference.html
There is only one enumeration method. So this is the only way.
I needed the last imported photos. You can have some filter similar to this.
[[assetsLibrary] enumerateGroupsWithTypes:ALAssetsGroupAlbum usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
if( group )
{
NSString * groupName = [group valueForProperty:ALAssetsGroupPropertyName];
if( [#"Last Import" isEqualToString:groupName] )
{
*stop = true;
...
The accepted answer doesn't appear to work if you're enumerating an ALAssetGroup that you've set a filter on (because [group numberOfAssets] returns the total assets rather then the total assets after filtering).
I used this:
typedef void(^SMKMostRecentPhotoCompletionBlock)(ALAsset *asset);
- (void)mostRecentPhotoWithCompletionBlock:(SMKMostRecentPhotoCompletionBlock)completionBlock
{
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
__block ALAsset *mostRecentPhoto = nil;
if (group)
{
[group setAssetsFilter:[ALAssetsFilter allPhotos]];
[group enumerateAssetsWithOptions:NSEnumerationReverse usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
if (result != NULL)
{
mostRecentPhoto = result;
*stop = YES;
}
}];
}
if (completionBlock)
{
completionBlock(mostRecentPhoto);
}
} failureBlock:^(NSError *error) {
if (completionBlock)
{
completionBlock(nil);
}
}];
}
In your completionBlock, make sure to check that the returned ALAsset != nil.