Cloning UIImagePickerController using the Assets Library Framework

October 7th, 2010 Posted by: (ELC) - posted under:Featured » Tutorials

Displaying ALAssets using Asset

Asset is the final class which comprises the ELCImagePickerController. Asset is a UIView subclass which takes in an ALAsset and creates a view that represents it. The final view will be a square thumbnail of the ALAsset that is passed in along with a checkmark overlay that is very similar to the checkmark used in Apple’s Photos application. In AssetCell we set up the UITapGestureRecognizer that will call this method whenever an asset is tapped. ALAssets, which we inject into an Asset object when creating, have a convenience method called thumbnail that we can utilize to get the actual image that will represent the asset. After adding that to the view we add the image view for the overlay and set it as hidden. Our toggleOverlay: method will check whether that view is hidden. Finally we have a checker to see if the asset is selected with the method selected. If the overview is hidden the asset is not selected and if it is visible it is selected. All this comes together to form the final smarts we need to make our picker functional. See the header and main below.

Asset.h

@interface Asset : UIView {
	ALAsset *asset;
	UIImageView *overlayView;
	BOOL selected;
	id parent;
}
 
@property (nonatomic, retain) ALAsset *asset;
@property (nonatomic, assign) id parent;
 
-(id)initWithAsset:(ALAsset*)_asset;
-(BOOL)selected;
 
@end

Asset.m

@implementation Asset
 
@synthesize asset;
@synthesize parent;
 
- (id)initWithFrame:(CGRect)frame {
    if ((self = [super initWithFrame:frame])) {
        // Initialization code
    }
    return self;
}
 
-(id)initWithAsset:(ALAsset*)_asset {
 
	if (self = [super initWithFrame:CGRectMake(0, 0, 0, 0)]) {
 
		asset = _asset;
		[asset retain];
 
		CGRect viewFrames = CGRectMake(0, 0, 75, 75);
 
		UIImageView *assetImageView = [[UIImageView alloc] initWithFrame:viewFrames];
		[assetImageView setContentMode:UIViewContentModeScaleToFill];
		[assetImageView setImage:[UIImage imageWithCGImage:[asset thumbnail]]];
 
		[self addSubview:assetImageView];
		[assetImageView release];
 
		overlayView = [[UIImageView alloc] initWithFrame:viewFrames];
		[overlayView setImage:[UIImage imageNamed:@"Overlay.png"]];
		[overlayView setHidden:YES];
		[self addSubview:overlayView];
    }
 
	return self;
}
 
-(void)toggleSelection {
 
	overlayView.hidden = !overlayView.hidden;
}
 
-(BOOL)selected {
 
	return !overlayView.hidden;
}
 
-(void)setSelected:(BOOL)_selected {
 
	[overlayView setHidden:!_selected];
}
 
- (void)dealloc {
 
	[asset release];
	[overlayView release];
    [super dealloc];
}
@end

Passing the Assets Back Up

So now that all the assets can be displayed and selected lets take a look at how they are passed up the chain. First thing to know is that the ELCImagePickerController passes up Asset objects, since they contain references to the ALAsset which they represent. With that said the place we start is in the AssetTablePicker. When dismiss is clicked there the class calls the dismiss method which looks through the assets array and checks for which one is selected. It adds the selected Asset objects to an array and then calls AlbumPickerControllers selectedAssets: method. The selectedAssets method just passes this array along to the ELCImagePickerController method selectedAssets:. Here a dictionary is constructed to represent every asset that was selected. The dictionary contains the same information that is delivered by UIImagePickerController when it delivers assets. This way the ELCImagePickerController can be included into projects as simply as possible. Below you will see each of the passback methods described above as well as the definition of the ELCImagePickerControllerDelegate Protocol which defines the methods that should be implemented on the calling class.

-(IBAction)dismiss:(id)sender {
 
	NSMutableArray *selectedAssetsImages = [[NSMutableArray alloc] init];
 
	for(Asset *asset in assets) {
 
		if([asset selected]) {
 
			[selectedAssetsImages addObject:[asset asset]];
		}
	}
 
	[(AlbumPickerController*)parent selectedAssets:[NSArray arrayWithArray:selectedAssetsImages]];
}
-(void)selectedAssets:(NSArray*)_assets {
 
	[(ELCImagePickerController*)parent selectedAssets:_assets];
}
-(void)selectedAssets:(NSArray*)_assets {
 
	NSMutableArray *returnArray = [[NSMutableArray alloc] init];
 
	for(ALAsset *asset in _assets) {
 
		NSMutableDictionary *workingDictionary = [[NSMutableDictionary alloc] init];
		[workingDictionary setObject:[asset valueForProperty:ALAssetPropertyType] forKey:@"UIImagePickerControllerMediaType"];
		[workingDictionary setObject:[UIImage imageWithCGImage:[[asset defaultRepresentation] fullScreenImage]] forKey:@"UIImagePickerControllerOriginalImage"];
		[workingDictionary setObject:[[asset valueForProperty:ALAssetPropertyURLs] valueForKey:[[[asset valueForProperty:ALAssetPropertyURLs] allKeys] objectAtIndex:0]] forKey:@"UIImagePickerControllerReferenceURL"];
 
		[returnArray addObject:workingDictionary];
 
		[workingDictionary release];
	}
 
	if([delegate respondsToSelector:@selector(elcImagePickerController:didFinishPickingMediaWithInfo:)]) {
		[delegate performSelector:@selector(elcImagePickerController:didFinishPickingMediaWithInfo:) withObject:self withObject:[NSArray arrayWithArray:returnArray]];
	}
}
@protocol ELCImagePickerControllerDelegate
 
- (void)elcImagePickerController:(ELCImagePickerController *)picker didFinishPickingMediaWithInfo:(NSArray *)info;
- (void)elcImagePickerControllerDidCancel:(ELCImagePickerController *)picker;
 
@end

GIT Hub

You can find this project now on GitHub. Please let me know any issues you may have and look for future releases with feature enhancements. Happy coding!

Follow me on Twitter @cruffenach

Pages: 1 2 3 4

  • http://www.architek.co.uk Free1000

    Thanks for making this open, I think its highly useful keeping this cloned to the appearance of the existing ImagePickerController.

    So far I’ve successfully run this in the simulator on the 4.2 beta SDK. The initial app doesn’t seem to run on my iPad with the 4.2 beta on it currently though, The initial ELCImagePickerDemoViewController doesn’t display the button. No idea why at the moment but if I have time next week to work with this a bit more I’ll let you know as I will try to integrate this into my own app and see how it goes later in the week.

    One question I have is how does the memory get reclaimed in the case of the following method
    -(void)selectedAssets:(NSArray*)_assets {

    NSMutableArray *returnArray = [[NSMutableArray alloc] init];

    for(ALAsset *asset in _assets) {

    NSMutableDictionary *workingDictionary = [[NSMutableDictionary alloc] init];
    [workingDictionary setObject:[asset valueForProperty:ALAssetPropertyType] forKey:@”UIImagePickerControllerMediaType”];
    [workingDictionary setObject:[UIImage imageWithCGImage:[[asset defaultRepresentation] fullScreenImage]] forKey:@”UIImagePickerControllerOriginalImage”];
    [workingDictionary setObject:[[asset valueForProperty:ALAssetPropertyURLs] valueForKey:[[[asset valueForProperty:ALAssetPropertyURLs] allKeys] objectAtIndex:0]] forKey:@”UIImagePickerControllerReferenceURL”];

    [returnArray addObject:workingDictionary];

    [workingDictionary release];
    }

    if([delegate respondsToSelector:@selector(elcImagePickerController:didFinishPickingMediaWithInfo:)]) {
    [delegate performSelector:@selector(elcImagePickerController:didFinishPickingMediaWithInfo:) withObject:self withObject:[NSArray arrayWithArray:returnArray]];
    }

    I’m relatively new to Obj-C and Cocoa touch from Java development so some memory handling remains for me to fully understand. Given that you allocate the member returnArray and then pass it via the delegate method does this mean the delegate needs to know that it has to release the returnArray itself? Or is there some other way the memory is released. I’d assume ( possibly incorrectly) that the ELCImagePickerController class would retain the NSMutableArray.

    Also, I haven’t yet implemented a delegate and I’m fascinated by this particular pattern.

    if([delegate respondsToSelector:@selector(elcImagePickerController:didFinishPickingMediaWithInfo:)]) {
    [delegate performSelector:@selector(elcImagePickerController:didFinishPickingMediaWithInfo:) withObject:self withObject:[NSArray arrayWithArray:returnArray]];

    As I have no idea to start with the implementation of delegates in Obj-c this is very instructional.

  • http://www.architek.co.uk Free1000

    PS: Although your source is open it is copyrighted and I can’t see any information about licensing. Do you have any information about this?

    • http://www.architek.co.uk Free1000

      Oops, just seen the MIT license! Ignore previous.

  • sam

    Hello,

    I found a major bug in ur app while syncing the photos.I have also made the slideshow using AssetsLibrary and facing the same problem.

    Just sync some new photos using itunes and try to acess those photos,only thumbail wd be shown and not the fullscreen/fullres images.

    Pls let me know if you have any solution to this.

    thanks

  • ct

    Any ideas how to implement selection of all the images from the AssetTablePicker..?

  • http://www.cokersolutions.com/forum mracoker

    This is great and thank you for making it available. I have question, I would like to show a UIView after the users selects their images. If a user selects 20 or more it appears that the app locks up, it doesnt it just looks that way. So I want to show a sort of “Please Wait” so the user sees that the app is busy. How is the best way to implement this?

    Thanks,

    Albert C.

    • http://www.cokersolutions.com/forum mracoker

      I figured it out on my own.

      I basically had to show a sub view that is semi-transparent, remove the back button and done button.

      Next I had to show my sub view when the dismiss button was clicked in -(void)selectedAssets:(NSArray*)_assets in the AssetTablePicker implementation.

      Then I had to move -(void)selectedAssets:(NSArray*)_assets in ELCImagePickerController implementation to a background thread so the ui doesn’t lock up.

      Thanks,

      Albert C.

  • John

    This is great. Thank you for the wonderful post!

    Question: How would we make this so the user is limited to picking only one image?

    Thanks again!

  • http://worldlbizexpress.com Bharath Rai

    First I would like to thank for this great tutorial.
    I come across one problem with this, when we have more number of images in image library, the application will crash. I think, because of putting images in array(memory issue). Is it possible to save images in database(sqlite, local memory)and then show in scroll view.

  • paolo

    Please, help.
    How to use it?

    ELCImagePickerController *controller = [[ELCImagePickerController alloc] initImagePicker];
    [controller setDelegate:self];
    [self presentModalViewController:controller animated:YES];
    //This is an error:
    [controller selectedAssets:MY_IMAGE_ARRAY];??
    //Thank you

    [controller release];

    • http://www.cokersolutions.com/forum Albert Coker

      It looks like you are trying to pass a array of images to the picker. That is not how it works. ELCImagePickerController allows you to pick images, then passes them back via the delegate to where ever you initiated it from.

  • CoderGirl

    Hi, firstly thank you very much for the code. The code runs perfectly well in Simulator 4.1 but when I try 4.0 or 4.2 it gets stuck on the Loading screen and it gives me the following comment on the GDB
    2010-11-16 16:37:27.514 ELCImagePickerDemo[49819:6b03] A problem occured

    Its from this line in the code.

    ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
    [library enumerateGroupsWithTypes:ALAssetsGroupAll
    usingBlock:assetGroupEnumerator
    failureBlock:^(NSError *error) {
    NSLog(@"A problem occured");
    }];
    Somehow I am not able to figure out what is the problem, I have added the Asset Lib framework and everything. Please help.

    • DaveR

      CoderGirl, were you ever able to run this on a 4.2 device?

      • CoderGirl

        Hi, yes its working on 4.2, I had to makes some changes though to suit my needs, following is the change I made :-

        -(void)preparePhotos {

        dispatch_async(dispatch_get_main_queue(), ^{

        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        NSLog(@”hmmm 1″);
        void (^assetGroupEnumerator)(ALAssetsGroup *, BOOL *) = ^(ALAssetsGroup *group, BOOL *stop) {
        NSLog(@”hmmm 2″);
        if(group != nil) {
        [assetGroups addObject:group];
        NSLog(@”Number of assets in group %d”, [group numberOfAssets]);
        }
        else {
        NSLog(@”group is nil”);
        }

        [self performSelectorOnMainThread:@selector(reloadTableView) withObject:nil waitUntilDone:NO];
        };

        assetGroups = [[NSMutableArray alloc] init];

        ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
        [library enumerateGroupsWithTypes:ALAssetsGroupAll
        usingBlock:assetGroupEnumerator
        failureBlock:^(NSError *error) {
        NSLog(@"A problem occurred %@",error);
        }];
        [library release];
        [pool release];
        NSLog(@”hmmm 3″);

        });
        }

        Hope it works for you.

  • samar

    nice article!!!

  • Andrew

    Hi,

    Great article. Just having some problems with the source code provided. Using simulator 4.2. and the latest xcode (downloaded last night), It builds fine, loads the album viewer, but when it loads up the images in the album, and then I select them, all the images just disappear. When I try clicking anywhere else it just crashes.

    Console doesn’t spit out anything either which is annoying because I cant see whats going wrong.

    Help?

  • Paul Freeman

    I’ve found a problem with the code that prepares the group table. A couple of subtle problems are happening that I don’t yet understand. However, if you remove the NSLog statement from the closure then two problems appear.

    ie : remove the line

    NSLog(@”Number of assets in group=%d”, [group numberOfAssets]);

    from the prepareImages method in the group level picker

    void (^assetGroupEnumerator)(struct ALAssetsGroup *, BOOL *) = ^(ALAssetsGroup *group, BOOL *stop) {
    if(group != nil) {
    [assetGroups addObject:group];
    //NSLog(@”Number of assets in group=%d”, [group numberOfAssets]);
    }
    [self performSelectorOnMainThread:@selector(reloadTableView) withObject:nil waitUntilDone:NO];
    };

    Now there are two things that occur

    1. The count numbers for number of assets may not appear in the subsequent code that formats the table rows. Sometimes the count is incorrectly stated as zero.

    2. If the user does not create an albums in the photo library but just syncs their images with the library as a whole, then when you pick the ‘PhotoLibrary’ group or the ‘Shared Photos’ group (these are the only ones which are visible in this case) then the user will see no photos found.

    I’ve replaced the NSLog line with the following

    int count = [group numberOfAssets]; //ignore the warning from not using ‘count’

    And the code still works with this, so it looks as though there is something subtle going on here with how the closure is executing. I’ve no idea if this is a bug with the AssetsLibrary, but its certainly worth looking at as it could cause a serious problem with an app.

    • Jed Lau

      I’ve experienced this problem as well in my own code. Have you uncovered any reasons why this might be occurring? Thanks.

      • brian

        Have you guys figured anything out on this front? I experienced the same problem. I ended up replacing the NSLog line with just “[group numberOfAssets];”. I just noticed an update to the ELCImagePicker code though, so perhaps there’s a fix in there?

  • Felix

    Trying to get this to work so far so good however my “DONE” button is not appearing when your in the picker it shows the cancel fading out but then fading back in it doesnt change to done so i cant really chose any images. Where do you change the nav bar from cancel to done?

  • http://hubpages.com/profile/larryfreeman Larry

    When I run it, it seems that the orientation of the image gets lost. When I check the imageOrientation that gets saved in the dictionary, it also returns:

    UIImageOrientationUp

    Here’s how I’m checking the orientation.


    UIImage *image = [dict objectForKey:UIImagePickerControllerOriginalImage];
    UIImageOrientation orient = image.imageOrientation;

    If you have any suggestions for figuring out the correct orientation of the image added to the dictionary, that would be great.

    Thanks,

    -Lary

    • http://hubpages.com/profile/larryfreeman Larry

      OK, I figured it out how to add orientation to the code. Here’s the code:

      ALAssetRepresentation *representation = [asset defaultRepresentation];
      CGImageRef imageRef = [representation fullScreenImage];
      ALAssetOrientation orientation = [representation orientation];
      UIImage *image = [UIImage imageWithCGImage:imageRef scale:1.0 orientation:(UIImageOrientation)orientation];

      [workingDictionary setObject:image forKey:@"UIImagePickerControllerOriginalImage"];

  • DesiGuy421

    Hi,

    Thanks for making this. This is saving me time instead of having to roll my own.

    With that said, there are several places in this code with glaring memory leaks. I have already forked and committed a fix for one that I have submitted a pull request for. There are others that I’ve discovered that could’ve been avoided with an autorelease call but are much more deeply rooted, and I am, as of right now, unsure of what consequences might result in fixing them.

    Regards.

  • http://oozware.com Jung

    Hi. thanks for sharing such a good code

    when i use your code, I suddenly have a little thing to ask

    have you tried to load photo library that over 500 photos inside?

    when i load more than 500 photos it takes so long to load whole images

    can i have your advice to get rid of this one?

  • Stefan

    I tried your sample code on several iPhones and it’s working only if there are not so many pictures stored on the device, once a folder has more then 200 images there is a delay and its shows loading for a few seconds.

    However, I have one iPhone 4 with more then 800 images in several folders and it doesn’t even go past the “loading” stage.

    I have one app called ” Stash Pro” (hedonic software) and they are able to open the photo library without any delay (even with 800 images) and you can import several pictures or even the complete folder.

    Did they use a modified version of this script ? Or does anyone know how they made it ?

    Thanks for your advise.

    • Jabroni

      I’m just checking to see if you were able to resolve this? I have the same issue.

  • CodeBear

    It is not working on my iPhone 3GS with iOS4.2.1. It worked on the simulator but it seems that it’s not able to find the photo directory in the device. The behaviour is similar to no photo in the simulator. It said “Loading…”

  • http://twitter.com/haentz Hans Schneider

    Thanks a lot for making this library available! Unfortunately I have the same issue as CodeBear. On both my 4.2.1 developer phones and also on my 4.3beta iPad the assetGroupEnumerator does not work. The last output in the console is:
    sandboxd[944] : ELCImagePickerDe(942) deny file-write-data /private/var/mobile/Media/PhotoData

    It looks like the system is keeping the app from accessing the album data (which indeed is outside the app sandbox). I read through Apple’s documentation on Assets and tried various stuff but I always end up with this error. Does anyone have an idea what the issue might be? Has anyone probably tested the code on a non-developer iPhone running 4.2.1 (with an ad hoc certificate for example or with an app from the app store)?

    At the moment I’m really a bit lost with this problem. Of course I understand it’s not an issue with ELCImagePickerController, but rather with the iPhone 4.2.1 API and/or system.

  • http://twitter.com/haentz Hans Schneider

    Ok, looks like I found the problem. It seems that you can only make calls to the ALAssetsLibrary in the app’s main thread in 4.2 (or probably even earlier.

    I changed the following line in the implementation of ELCImagePickerController.

    [self performSelectorInBackground:@selector(preparePhotos) withObject:nil];
    becomes
    [self preparePhotos];

    • brian

      Hi, I believe that line exists in both the AssetTablePicker and AlbumPickerController classes. Did you change both?

  • SivaKumar

    Hi ,

    i buildup this code to iPad.i am using IOS SDK 4.2.When i’m pressing the “Launch ELC Image Picker Controller Button” it shows only empty table still the navigation bar holds the message “Loading..”.But in simulator it works fine.Please help to short it out.

  • addon.hrushikesh

    i am new in iphone. i want to develop the webservice application. i am developing the application for the welcome view and it is reading the message from the webservice xml file .i am puting the code in the -viewdidload function but it is displaying the message.

  • http://twitter.com/Designergianna gianna

    I am a newbie to i-phone, i came across this word cloning in Java long back, user interface is more important because most of the people rely on user friendly application,..It seems that you can only make calls to the ALAssetsLibrary in the app’s main thread in 4.2 (or probably even earlier.

  • Pingback: Update: ELCImagePickerController | iPhone Programming Tutorials

  • Miles_c

    Very nice tutorial, especially the exposure to ALAssets. I’m trying to get some persistence on the overlayView that was checked by the user to show up the next time they enter the AssetTablePicker. I thought I might be able to do this by setting the ELCAsset self.selected in the toggleSelection method. When I look at self.elcAssets in doneAction it shows all the Assets that have been selected, but when I try to access self.elcAssets in preparePhotos that array is empty, is that because of threading?

    Also, would it be better to iterate through the Assets during doneAction and write them out to a .plist then read them back in just before Enumerating them in preparePhotos?

  • Stfoll

    Hello all, Thanks fort this app, it works wonderful, but how can I deselect a thumb programmatically ? I tried a lot of ways , but no success . Thanks

    • Stfoll

      Oops sorry, it is so easy :) ) setSelected:(BOOL)

      • martin

        ;;

  • http://twitter.com/jonathancarroll Jon Carroll

    I am having a pretty weird issue with the picker and was wondering if anyone might have some ideas. I am using the picker in an iPad application that allows you to import both images and video. I am using the ELCImagePicker for importing images and the standard UIImagePickerController for video.

    The problem is that after displaying the UIImagePicker controller and successfully bringing in a video the ELCImagePickerController will only display folder/album counts for the number of videos in each album and will then show no pictures or video in any of the albums. If I present the ELCImagePicker controller before importing a video with UIImagePickerController it works fine.

    I suspect this may be a bug as I have looked through the ELCImagePicker code and you seem to be doing everything right. Any thoughts are appreciated. Thanks!

  • aofeng2009

    Good tutorial , When an app accesses the assets library for the first time an alert view vill pop-up to ask the user to allow the access to the device’s assets. If i tap the “not allowed”, The system will always forbid me access the assets even if i restart the app,how can i access the assets second?

    • http://twitter.com/jonathancarroll Jon Carroll

      You might have to re-install the app after denying it, not certain though.

      I present a UIAlertView to the user before the image picker comes up for the first time explaining why it is about to ask for their location and that the functionality won’t work without it. Seems to work well, haven’t had any users complain yet.

  • Timo

    you have to go to the iphone settings and then grant the app access there. It would be a nicer way to catch the error and show a pop up that explains the user why he has to activate this setting. And direct him to the settings. Any idea how to do this?

    • aofeng2009

      How can i grant the app access ,I can’t find that in iphone’s settings.Any idea?

  • imobilizer

    Hello,

    My app allows the user to pick photos and videos for upload via UIImagePickerController. My issue is that regardless of the UIImagePickerController.videoQuality value I set, once I Tap “Choose” on the preview screen the video gets transcoded (I see the compressing video progress bar). This is fine since I need to be sure videos can be uploaded over a cellular connection. The issue is that if I put the app into the background (while compressing) the compression seems to be halted. When I return to the app I see the progress bar has not moved and does not resume. What would be required to emulate the preview screen/choose/transcode functionality and manage the transcoding in the background or in a non-modal way using Assets Library or AVFoundation?

  • Will

    Thanks for writing this class. I’m having an issue with the picker when loading lots of images. I have 1358 images in the camera roll and memory warnings are sent by the OS to my app. In the most severe case the picker crashes with a bad access in the preparePhotos method call.

    Could you investigate?

    Thanks,
    Will

  • Ayon Ashish

    Please update it for ios 5

  • http://www.cokersolutions.com/forum mracoker

    @Sam
    I dont know about your thumbnail issue but I did notice that when using the ELCImagePickerController that I would not get the full resolution image. I have found how to change this and will share for you or others:

    Line #43 in ELCImagePickerController.m

    From:
    [workingDictionary setObject:[UIImage imageWithCGImage:[[asset defaultRepresentation] fullScreenImage]] forKey:@”UIImagePickerControllerOriginalImage”];

    To:
    [workingDictionary setObject:[UIImage imageWithCGImage:[[asset defaultRepresentation] fullResolutionImage]] forKey:@”UIImagePickerControllerOriginalImage”];

canakkale canakkale canakkale balik tutma search canakkale vergi mevzuati bagimsiz denetim vergi mevzuati ozurlu engelliler