Saving Images In iOS - objective-c

I have an app that behaves like a photo gallery. I'm implementing the ability for the user to delete the photos, by placing an invisible button over each UIImageView, and calling removeObject when they tap on the button. This code is working great, but its dependent upon tags. I need to tag every UIImageView / UIButton in interface builder in order for this to work. So, I'm now trying to save the images in a way that my tags will still work, which excludes using NSData.
So I'm totally lost on what to do right now. I'm very, very new to programming and am shocked that I even made it this far. Any help or advice on what or how to edit my code to make this work is much appreciated, thanks!
Here is my entire file just for reference:
- (IBAction)grabImage {
self.imgPicker = [[UIImagePickerController alloc] init];
self.imgPicker.delegate = self;
self.imgPicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
_popover = [[UIPopoverController alloc] initWithContentViewController:imgPicker];
[_popover presentPopoverFromRect:self.imageView.bounds inView:self.imageView permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
else {
[self presentModalViewController:imgPicker animated:YES];
}
[self.imgPicker resignFirstResponder];
}
// Sets the image in the UIImageView
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)img editingInfo:(NSDictionary *)editInfo {
if (imageView.image == nil) {
imageView.image = img;
[self.array addObject:imageView.image];
[picker dismissModalViewControllerAnimated:YES];
[self.popover dismissPopoverAnimated:YES];
return;
}
if (imageView2.image == nil) {
imageView2.image = img;
NSLog(#"The image is a %#", imageView);
[self.array addObject:imageView2.image];
[picker dismissModalViewControllerAnimated:YES];
[self.popover dismissPopoverAnimated:YES];
return;
}
if (imageView3.image == nil) {
imageView3.image = img;
[self.array addObject:imageView3.image];
[picker dismissModalViewControllerAnimated:YES];
[self.popover dismissPopoverAnimated:YES];
return;
}
if (imageView4.image == nil) {
imageView4.image = img;
[self.array addObject:imageView4.image];
[picker dismissModalViewControllerAnimated:YES];
[self.popover dismissPopoverAnimated:YES];
return;
}
if (imageView5.image == nil) {
imageView5.image = img;
[self.array addObject:imageView5.image];
[picker dismissModalViewControllerAnimated:YES];
[self.popover dismissPopoverAnimated:YES];
return;
}
- (void)applicationDidEnterBackground:(UIApplication*)application {
NSLog(#"Image on didenterbackground: %#", imageView);
[self.array addObject:imageView.image];
[self.array addObject:imageView2.image];
[self.array addObject:imageView3.image];
[self.array addObject:imageView4.image];
[self.array addObject:imageView5.image];
[self.user setObject:self.array forKey:#"images"];
[user synchronize];
}
- (void)viewDidLoad
{
self.user = [NSUserDefaults standardUserDefaults];
NSLog(#"It is %#", self.user);
self.array = [[self.user objectForKey:#"images"]mutableCopy];
imageView.image = [[self.array objectAtIndex:0] copy];
imageView2.image = [[self.array objectAtIndex:1] copy];
imageView3.image = [[self.array objectAtIndex:2] copy];
imageView4.image = [[self.array objectAtIndex:3] copy];
imageView5.image = [[self.array objectAtIndex:4] copy];
UIApplication *app = [UIApplication sharedApplication];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:app];
[super viewDidLoad];
}
// This is when the user taps on the image to delete it.
- (IBAction)deleteButtonPressed:(id)sender {
NSLog(#"Sender is %#", sender);
UIAlertView *deleteAlertView = [[UIAlertView alloc] initWithTitle:#"Delete"
message:#"Are you sure you want to delete this photo?"
delegate:self
cancelButtonTitle:#"No"
otherButtonTitles:#"Yes", nil];
[deleteAlertView show];
int imageIndex = ((UIButton *)sender).tag;
deleteAlertView.tag = imageIndex;
}
- (UIImageView *)viewForTag:(NSInteger)tag {
UIImageView *found = nil;
for (UIImageView *view in self.array) {
if (tag == view.tag) {
found = view;
break;
}
}
return found;
}
- (void)alertView: (UIAlertView *) alertView
clickedButtonAtIndex: (NSInteger) buttonIndex
{
if (buttonIndex != [alertView cancelButtonIndex]) {
NSLog(#"User Clicked Yes. Deleting index %d of %d", alertView.tag, [array count]);
NSLog(#"The tag is %i", alertView.tag);
UIImageView *view = [self viewForTag:alertView.tag];
if (view) {
[self.array removeObject:view];
}
NSLog(#"After deleting item, array count = %d", [array count]);
NSLog(#"Returned view is :%#, in view: %#", [self.view viewWithTag:alertView.tag], self.view);
((UIImageView *)[self.view viewWithTag:alertView.tag]).image =nil;
}
[self.user setObject:self.array forKey:#"images"];
}
#end

It seems you are using the tag just so you can identify which image view is being selected. You have an "invisible" button on top of each image? Is that right? I assume that is so you can handle a tap, which selects the image that is showing through the button?
There are lots of ways to do it, but a simple solution is to just recognize the tap, and "find" the image view underneath that tap. Drop a UITapGestureRecognizer onto your controller from within the storyboard. Ctrl-drag it into the code for your controller, and it will create an action method. Fill it in something like this...
- (IBAction)tapGesture:(UITapGestureRecognizer*)gesture
{
CGPoint tapLocation = [gesture locationInView: self.galleryView];
for (UIImageView *imageView in self.galleryView.subviews) {
if (CGRectContainsPoint(imageView.frame, tapLocation)) {
// This is the imageView that was tapped on!
// Do whatever you want with it now that you found it.
}
}
}

The previous comment is accurate; please think about how you're going to scale this up.
That said, you'll need to store and load images in some way, and that will always mean converting your UIImage objects to NSData. This applies whether you store them in NSUserDefaults, in files or in Core Data.
UIImage supports NSCoding, so you could use that. Read about how to use it, since you'll need it eventually. Or, if you know you'll always want to use PNG or JPEG formats, there are the functions UIImagePNGRepresentation(UIImage *image) and UIImageJPEGRepresentation(UIImage *image, CGFloat compressionQuality). To convert the NSData objects back to UIImage, use [UIImage imageWithData:NSData*] Here's what you could have in applicationDidEnterBackground:
NSMutableArray *dataArray = [NSMutableArray array];
[dataArray addObject:UIImagePNGRepresentation(imageView.image)];
[dataArray addObject:UIImagePNGRepresentation(imageView2.image)];
[dataArray addObject:UIImagePNGRepresentation(imageView3.image)];
[self.user setObject:dataArray forKey:#"images"];
[self.user synchronize];
Then, to retrieve these during viewDidLoad:
[self.array removeAllObjects];
NSArray *dataArray = [self.user objectForKey:#"images"];
for (NSData *imageData in dataArray)
[self.array addObject:[UIImage imageWithData:imageData]];
This will solve your immediate problem, but please don't consider it a permanent solution. Consider:
Store your images in files or Core Data, as NSUserDefaults is not
supposed to have large amounts of content.
Use a UITableView to display the images, so you can have an
arbitrary number instead of hard-coding for three.
If UITableView doesn't have the layout you need, create a custom
UIView subclass that displays an array of images and responds to taps appropriately.
Make sure your application can detect all the ways it may be
suspended or shut down. You may not always get the
ApplicationDidEnterBackground notification, or you may not have time
to save all your data after it happens. If you have multiple
UIViewControllers, this one may be unloaded without the application
itself receiving notifications.

Related

iOS: Image Will Not Delete From Array

I'm working on an app that behaves like a photo gallery, and I'm implementing the option to have the user delete photos from their gallery. To accomplish this, I decided to place an invisible button over each picture. When the user hits an "Edit" button, the hidden delete buttons over each picture become active. I'm using the same IBOutlet over each of the hidden buttons for simplicity, and I've tagged each button appropriately in Interface Builder. When the user taps the button over the picture, an alert view appears asking if they really want to delete it. If they click yes, I call removeObjectAtIndex. Here is the code I'm using:
- (IBAction)deleteButtonPressed:(id)sender {
NSLog(#"Sender is %#", sender);
UIAlertView *deleteAlertView = [[UIAlertView alloc] initWithTitle:#"Delete"
message:#"Are you sure you want to delete this photo?"
delegate:self
cancelButtonTitle:#"No"
otherButtonTitles:#"Yes", nil];
[deleteAlertView show];
int imageIndex = ((UIButton *)sender).tag;
deleteAlertView.tag = imageIndex;
}
- (void)alertView: (UIAlertView *) alertView
clickedButtonAtIndex: (NSInteger) buttonIndex
{
if (buttonIndex != [alertView cancelButtonIndex]) {
NSLog(#"User Clicked Yes.");
[self.array removeObjectAtIndex: alertView.tag];
}
[self.user setObject:array forKey:#"images"];
}
The issue here is that when I click "Yes" in the alert view, nothing happens. However, if I tap on the image and click "Yes" a second time, the app crashes, and the debug states:
Terminating app due to uncaught exception 'NSRangeException', reason: '-[__NSCFArray removeObjectAtIndex:]: index (0) beyond bounds (0)' So, I'm not sure where to go from here, I'm still very new to programming and everything looks correct to me. Any help is much appreciated, thanks!
Here is how I add them into the array:
////start of saving////
- (void)viewWillAppear:(BOOL)animated
{
self.user = [NSUserDefaults standardUserDefaults];
self.array = [[self.user objectForKey:#"images"]mutableCopy];
while(self.array == nil)
{
[self.user setObject:[NSMutableArray arrayWithObject:#""] forKey:#"images"];
self.array = [[self.user objectForKey:#"images"]mutableCopy];
NSLog(#"%#",#"attempting to create an array to store the images in");
}
}
- (void)applicationDidEnterBackground:(UIApplication*)application {
NSLog(#"Image on didenterbackground: %#", imageView);
self.array = [NSMutableArray arrayWithObject:[NSData dataWithData:UIImagePNGRepresentation(imageView.image)]];
[self.array addObject:[NSData dataWithData:UIImagePNGRepresentation(imageView2.image)]];
[self.array addObject:[NSData dataWithData:UIImagePNGRepresentation(imageView3.image)]];
[self.array addObject:[NSData dataWithData:UIImagePNGRepresentation(imageView4.image)]];
[self.array addObject:[NSData dataWithData:UIImagePNGRepresentation(imageView5.image)]];
[self.user setObject:self.array forKey:#"images"];
[user synchronize];
}
- (void)viewDidLoad
{
self.user = [NSUserDefaults standardUserDefaults];
NSLog(#"It is %#", self.user);
self.array = [[self.user objectForKey:#"images"]mutableCopy];
imageView.image = [[UIImage alloc] initWithData:[self.array objectAtIndex:0]];
imageView2.image = [[UIImage alloc] initWithData:[self.array objectAtIndex:1]];
imageView3.image = [[UIImage alloc] initWithData:[self.array objectAtIndex:2]];
imageView4.image = [[UIImage alloc] initWithData:[self.array objectAtIndex:3]];
imageView5.image = [[UIImage alloc] initWithData:[self.array objectAtIndex:4]];
imageView6.image = [[UIImage alloc] initWithData:[self.array objectAtIndex:5]];
UIApplication *app = [UIApplication sharedApplication];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:app];
backToGalleryButton.hidden = YES;
tapToDeleteLabel.hidden = YES;
deleteButton1.hidden = YES;
[super viewDidLoad];
}
- (void)viewDidUnload
{
self.user = nil;
}
////end of saving
///// shows the hidden and invisible "delete" button over each photo.
- (IBAction)editButtonPressed:(id)sender {
grabButton.hidden = YES;
editButton.hidden = YES;
backToGalleryButton.hidden = NO;
tapToDeleteLabel.hidden = NO;
deleteButton1.hidden = NO;
}
- (IBAction)deleteButtonPressed:(id)sender {
NSLog(#"Sender is %#", sender);
UIAlertView *deleteAlertView = [[UIAlertView alloc] initWithTitle:#"Delete"
message:#"Are you sure you want to delete this photo?"
delegate:self
cancelButtonTitle:#"No"
otherButtonTitles:#"Yes", nil];
[deleteAlertView show];
int imageIndex = ((UIButton *)sender).tag;
deleteAlertView.tag = imageIndex;
}
- (void)alertView: (UIAlertView *) alertView
clickedButtonAtIndex: (NSInteger) buttonIndex
{
if (buttonIndex != [alertView cancelButtonIndex]) {
NSLog(#"User Clicked Yes.");
NSLog(#"Array: %#, index: %d", self.array, alertView.tag);
[self.array removeObjectAtIndex: alertView.tag];
}
[self.user setObject:array forKey:#"images"];
}
#end
EDIT: This is the code I use to put the objects into the UIImageView from the users camera roll:
- (IBAction)grabImage {
self.imgPicker = [[UIImagePickerController alloc] init];
self.imgPicker.delegate = self;
self.imgPicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
_popover = [[UIPopoverController alloc] initWithContentViewController:imgPicker];
[_popover presentPopoverFromRect:self.imageView.bounds inView:self.imageView permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
else {
[self presentModalViewController:imgPicker animated:YES];
}
[self.imgPicker resignFirstResponder];
}
// Sets the image in the UIImageView
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)img editingInfo:(NSDictionary *)editInfo {
if (imageView.image == nil) {
imageView.image = img;
[picker dismissModalViewControllerAnimated:YES];
[self.popover dismissPopoverAnimated:YES];
return;
}
if (imageView2.image == nil) {
imageView2.image = img;
[picker dismissModalViewControllerAnimated:YES];
[self.popover dismissPopoverAnimated:YES];
return;
}
if (imageView3.image == nil) {
imageView3.image = img;
[picker dismissModalViewControllerAnimated:YES];
[self.popover dismissPopoverAnimated:YES];
return;
}
if (imageView4.image == nil) {
imageView4.image = img;
[picker dismissModalViewControllerAnimated:YES];
[self.popover dismissPopoverAnimated:YES];
return;
}
}
When I added NSLog(#"Array: %#, index: %d", self.array, alertView.tag); just before removeAtIndex the console says 2012-04-03 18:39:39.066 AppName[1631:f803] Array: (null), index: 0. Could that be the cause? I'm not sure why it would be, I think the code looks fine.
Removing the image from the array is not the only step you have to take. Your code is correct for removing the image from the array, which is why you get image out of bounds the second time, but you also need to remove the image from the UI so the user can no longer delete an image that is not there.
There are many oddities with this code but I think the problem is that you are not calling super on your viewwillappear and viewdidload functions. I would get rid of your viewWillAppear method as it serves no purpose.

-initWithContentsOfFile: for an NSMutableArray

I stored images in an NSMutableArray, and now I'm trying to get them to show up in viewDidLoad. I tried calling initWithContentsOfFile, but that doesn't seem to work. This is how the code looks:
imageView.image = [[UIImage alloc] initWithContentsOfFile:[self.array objectAtIndex:0]];
I'm not sure what I should use instead of initWithContentsOfFile to have the saved images load, I'm not even sure if I can save images in a plist through user defaults. I've been researching it for awhile now to no avail. Any help is much appreciated, thanks!
EDIT: Here is additional code:
- (IBAction)grabImage {
self.imgPicker = [[UIImagePickerController alloc] init];
self.imgPicker.delegate = self;
self.imgPicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
_popover = [[UIPopoverController alloc] initWithContentViewController:imgPicker];
[_popover presentPopoverFromRect:self.imageView.bounds inView:self.imageView permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
else {
[self presentModalViewController:imgPicker animated:YES];
}
[self.imgPicker resignFirstResponder];
}
// Sets the image in the UIImageView
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)img editingInfo:(NSDictionary *)editInfo {
if (imageView.image == nil) {
imageView.image = img;
[self.array addObject:imageView.image];
[picker dismissModalViewControllerAnimated:YES];
[self.popover dismissPopoverAnimated:YES];
return;
}
if (imageView2.image == nil) {
imageView2.image = img;
NSLog(#"The image is a %#", imageView);
[self.array addObject:imageView2.image];
[picker dismissModalViewControllerAnimated:YES];
[self.popover dismissPopoverAnimated:YES];
return;
}
if (imageView3.image == nil) {
imageView3.image = img;
[self.array addObject:imageView3.image];
[picker dismissModalViewControllerAnimated:YES];
[self.popover dismissPopoverAnimated:YES];
return;
}
}
- (void)applicationDidEnterBackground:(UIApplication*)application {
NSLog(#"Image on didenterbackground: %#", imageView);
[self.array addObject:imageView.image];
[self.array addObject:imageView2.image];
[self.array addObject:imageView3.image];
[self.user setObject:self.array forKey:#"images"];
[user synchronize];
}
- (void)viewDidLoad
{
self.user = [NSUserDefaults standardUserDefaults];
NSLog(#"It is %#", self.user);
self.array = [[self.user objectForKey:#"images"]mutableCopy];
imageView.image = [[UIImage alloc] initWithContentsOfFile:[self.array objectAtIndex:0]];
imageView2.image = [[UIImage alloc] initWithContentsOfFile:[self.array objectAtIndex:1]];
imageView3.image = [[UIImage alloc] initWithContentsOfFile:[self.array objectAtIndex:2]];
UIApplication *app = [UIApplication sharedApplication];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:app];
backToGalleryButton.hidden = YES;
tapToDeleteLabel.hidden = YES;
deleteButton1.hidden = YES;
[super viewDidLoad];
}
EDIT: This is how I'm tagging the images and deleting them based upon their tags:
- (IBAction)deleteButtonPressed:(id)sender {
NSLog(#"Sender is %#", sender);
UIAlertView *deleteAlertView = [[UIAlertView alloc] initWithTitle:#"Delete"
message:#"Are you sure you want to delete this photo?"
delegate:self
cancelButtonTitle:#"No"
otherButtonTitles:#"Yes", nil];
[deleteAlertView show];
int imageIndex = ((UIButton *)sender).tag;
deleteAlertView.tag = imageIndex;
}
- (UIImageView *)viewForTag:(NSInteger)tag {
UIImageView *found = nil;
for (UIImageView *view in self.array) {
if (tag == view.tag) {
found = view;
break;
}
}
return found;
}
- (void)alertView: (UIAlertView *) alertView
clickedButtonAtIndex: (NSInteger) buttonIndex
{
if (buttonIndex != [alertView cancelButtonIndex]) {
NSLog(#"User Clicked Yes. Deleting index %d of %d", alertView.tag, [array count]);
NSLog(#"The tag is %i", alertView.tag);
UIImageView *view = [self viewForTag:alertView.tag];
if (view) {
[self.array removeObject:view];
}
NSLog(#"After deleting item, array count = %d", [array count]);
NSLog(#"Returned view is :%#, in view: %#", [self.view viewWithTag:alertView.tag], self.view);
((UIImageView *)[self.view viewWithTag:alertView.tag]).image =nil;
}
[self.user setObject:self.array forKey:#"images"];
}
The problem is that you can't store images in a property list, which is what you're trying to do when you save it in the user defaults. You need to use an archiver to convert the image to an NSData object which you can store.
It looks like you're not passing a valid image path to the initializer method. Make sure the path is correct, and that it includes the image extension.
Really, though, you shouldn't be calling initWithContentsOfFile: in this case, because UIImageView's image property retains the image when you set it. That will usually lead to a memory leak (unless you're using automatic reference counting). Use one of the static initializers instead, such as imageNamed:, which has the added bonuses of using the system cache and also automatically loading the correct version of the image based on the characteristics of the device (for instance, it will load a higher resolution variant of the image if the device has a retina display).

iOS: Simple NSMutableArray Call

I need to call removeObject in one of my methods, but I can't figure out how to do this correctly. I'm very new to Objective-C, and am still learning the basics. I have an app that behaves somewhat like a photo gallery, and displays UIImageViews. I'm implementing the option to have the user delete photos from their gallery. To accomplish this, I decided to place an invisible button over each picture. When the user hits an "Edit" button, the hidden delete button over each picture becomes active (I'm using the same IBOutlet over each of the hidden buttons, for simplicity). When the user taps the button over the picture, an alert view appears asking if they really want to delete it. If they click yes, deleteAlertView comes into play:
- (void)deleteAlertView:(UIAlertView *)deleteButtonPressed
didDismissWithButtonIndex:(NSInteger)buttonIndex {
if (buttonIndex != [deleteButtonPressed cancelButtonIndex]) {
[array removeObject:#"%#", deleteButtonPressed];
}
The issue here is [array removeObject:#"%#", deleteButtonPressed];, I did the %# so that this will automatically determine which object in the array was tapped, rather than manually putting in a new method and button for each UIImageView (I may have to end up doing that). I'm getting errors regarding "array" and "deleteButtonPressed" (use of undeclared identifier), I can't for the life of me figure out what to put instead. I'm still learning the basics and how inheritance in this language works. Any help or advice would be great! I should probably post the whole view controller file to show the related inheritance:
- (IBAction)grabImage {
self.imgPicker = [[UIImagePickerController alloc] init];
self.imgPicker.delegate = self;
self.imgPicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
_popover = [[UIPopoverController alloc] initWithContentViewController:imgPicker];
[_popover presentPopoverFromRect:self.imageView.bounds inView:self.imageView permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
else {
[self presentModalViewController:imgPicker animated:YES];
}
[self.imgPicker resignFirstResponder];
}
// Sets the image in the UIImageView
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)img editingInfo:(NSDictionary *)editInfo {
if (imageView.image == nil) {
imageView.image = img;
[picker dismissModalViewControllerAnimated:YES];
[self.popover dismissPopoverAnimated:YES];
return;
}
if (imageView2.image == nil) {
imageView2.image = img;
[picker dismissModalViewControllerAnimated:YES];
[self.popover dismissPopoverAnimated:YES];
return;
}
if (imageView3.image == nil) {
imageView3.image = img;
[picker dismissModalViewControllerAnimated:YES];
[self.popover dismissPopoverAnimated:YES];
return;
}
}
- (void)viewWillAppear:(BOOL)animated
{
self.user = [NSUserDefaults standardUserDefaults];
NSMutableArray* array = [[self.user objectForKey:#"images"]mutableCopy];
while(array == nil)
{
[self.user setObject:[NSMutableArray arrayWithObject:#""] forKey:#"images"];
array = [[self.user objectForKey:#"images"]mutableCopy];
NSLog(#"%#",#"attempting to create an array to store the images in");
}
}
- (void)applicationDidEnterBackground:(UIApplication*)application {
NSLog(#"Image on didenterbackground: %#", imageView);
NSMutableArray* array = [NSMutableArray arrayWithObject:[NSData dataWithData:UIImagePNGRepresentation(imageView.image)]];
[array addObject:[NSData dataWithData:UIImagePNGRepresentation(imageView2.image)]];
[array addObject:[NSData dataWithData:UIImagePNGRepresentation(imageView3.image)]];
[self.user setObject:array forKey:#"images"];
[user synchronize];
}
- (void)viewDidLoad
{
self.user = [NSUserDefaults standardUserDefaults];
NSLog(#"It is %#", self.user);
NSMutableArray* array = [[self.user objectForKey:#"images"]mutableCopy];
imageView.image = [[UIImage alloc] initWithData:[array objectAtIndex:0]];
imageView2.image = [[UIImage alloc] initWithData:[array objectAtIndex:1]];
imageView3.image = [[UIImage alloc] initWithData:[array objectAtIndex:2]];
UIApplication *app = [UIApplication sharedApplication];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:app];
backToGalleryButton.hidden = YES;
tapToDeleteLabel.hidden = YES;
deleteButton1.hidden = YES;
[super viewDidLoad];
}
- (IBAction)deleteButtonPressed:(id)sender {
UIAlertView *deleteAlertView = [[UIAlertView alloc] initWithTitle:#"Delete"
message:#"Are you sure you want to delete this photo?"
delegate:self
cancelButtonTitle:#"Yes"
otherButtonTitles:#"No", nil];
[deleteAlertView show];
}
- (void)deleteAlertView:(UIAlertView *)deleteButtonPressed
didDismissWithButtonIndex:(NSInteger)buttonIndex {
if (buttonIndex != [deleteButtonPressed cancelButtonIndex]) {
[array removeObject:#"%#", deleteButtonPressed];
}
}
There is one thing wring here, but first a relatively minor point:
- (void)deleteAlertView:(UIAlertView *)deleteButtonPressed didDismissWithButtonIndex:(NSInteger)buttonIndex
the phrase "deleteButtonPressed" implies an even, as it ends in a verb. It Is actually referring to an object, specifically a parameter of the type UIAlertView. you should call it something more like AlertView.
Secondly this line is quite wrong:
[array removeObject:#"%#", deleteButtonPressed];
You are trying to remove a string. If that method accepted an argument list (where you pass multiple objects separated by a comma), you would be removing literally "deleteButtonPressed". You want to remove the object that is being pointed to by the deleteButtonPressed variable. So all you have to do is:
[array removeObject:deleteButtonPressed];
The issue here is [array removeObject:#"%#", deleteButtonPressed];
Yes, that is one of the issues (even ignoring the invalid syntax). The array does not contain your UIAlertView, it contains whatever objects [user objectForKey:#"images"] contains. Which seem like they should be NSData instances and which in any case are definitely not your UIAlertView instance(s).
So in other words, you can't pass the UIAlertView to the array in order to have the array magically work out what item the UIAlertView is supposed to correspond to. Instead what you should do is tag the UIAlertView with the index it corresponds to when you create it. You can do this like:
UIAlertView *deleteAlertView = [[UIAlertView alloc] initWithTitle:#"Delete"
message:#"Are you sure you want to delete this photo?"
delegate:self
cancelButtonTitle:#"Yes"
otherButtonTitles:#"No", nil];
int imageIndex = <figure out the index of the associated array element based upon 'sender'>;
deleteAlertView.tag = imageIndex;
...and then when the button is pressed, you do:
[array removeObjectAtIndex:deleteButtonPressed.tag];
And to fix up that "undeclared identifier" issue, you should declare array in your header and not in viewDidLoad. You want it to be a private instance variable, not a local variable.
Also note that deleting an element from [[user objectForKey:#"images"] mutableCopy] will not automatically cause the corresponding element to be deleted from [user objectForKey:#"images"]. You need to write the modified array back to [NSUserDefaults standardUserDefaults] if you want the modification to actually persist.
You are getting the error "Use of undeclared identifier array" because you declare the array in different methods, but not in your deleteAlertView method. I suggest reading up on variable scope.
Fixing that, however, will not get your code to work because you have some fundamental design flaws that need to be worked out.
You mention that you are a beginner, so I would suggest reading through and completing several beginner tutorials before attempting this app. I know it is fun to dive right into a project, but you will very likely get frustrated and also develop bad habits with respect to app design/engineering. In particular, I would try to get a firmer understand of variable scope and MVC design patterns.

Xcode Unrecognized Selector Error

I've been having a ton of issues with this section of an app that I'm writing, I'm sure people here are getting sick of me so I'm going to try and solve all my questions in this post. I'm working on an app that behaves like a photo gallery, and I'm implementing the option to have the user delete photos from their gallery. To accomplish this, I decided to place an invisible button over each picture. When the user hits an "Edit" button, the hidden delete buttons over each picture become active. I'm using the same IBOutlet over each of the hidden buttons for simplicity, and I've tagged each button appropriately in Interface Builder. When the user taps the button over the picture, an alert view appears asking if they really want to delete it. If they click yes, I call removeObjectAtIndex and delete the image from the UI. But, when I click "Yes" in the alert view, I get an error from Xcode stating:
2012-04-04 11:26:40.484 AppName[608:f803] -[UIButton setImage:]: unrecognized selector sent to instance 0x6a922c0
2012-04-04 11:26:40.485 AppName[608:f803] Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIButton setImage:]: unrecognized selector sent to instance 0x6a922c0'*
I've been trying to figure out what is causing this for a few hours now to no avail. I'm not setting the image of a UIButton anywhere in my code. I did in IB, but I simply set the buttons types to Custom so that they appear invisible. I will post my entire file below, I can't find any issues in my code, so any help is much appreciated! Thanks.
EDIT Here is the current version of the code:
- (IBAction)grabImage {
self.imgPicker = [[UIImagePickerController alloc] init];
self.imgPicker.delegate = self;
self.imgPicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
_popover = [[UIPopoverController alloc] initWithContentViewController:imgPicker];
[_popover presentPopoverFromRect:self.imageView.bounds inView:self.imageView permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
else {
[self presentModalViewController:imgPicker animated:YES];
}
[self.imgPicker resignFirstResponder];
}
// Sets the image in the UIImageView
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)img editingInfo:(NSDictionary *)editInfo {
if (imageView.image == nil) {
imageView.image = img;
self.array = [NSMutableArray arrayWithObject:[NSData dataWithData:UIImagePNGRepresentation(imageView.image)]];
[picker dismissModalViewControllerAnimated:YES];
[self.popover dismissPopoverAnimated:YES];
return;
}
if (imageView2.image == nil) {
imageView2.image = img;
NSLog(#"The image is a %#", imageView);
[self.array addObject:[NSData dataWithData:UIImagePNGRepresentation(imageView2.image)]];
[picker dismissModalViewControllerAnimated:YES];
[self.popover dismissPopoverAnimated:YES];
return;
}
if (imageView3.image == nil) {
imageView3.image = img;
[self.array addObject:[NSData dataWithData:UIImagePNGRepresentation(imageView3.image)]];
[picker dismissModalViewControllerAnimated:YES];
[self.popover dismissPopoverAnimated:YES];
return;
}
if (imageView4.image == nil) {
imageView4.image = img;
[self.array addObject:[NSData dataWithData:UIImagePNGRepresentation(imageView4.image)]];
[picker dismissModalViewControllerAnimated:YES];
[self.popover dismissPopoverAnimated:YES];
return;
}
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.title = NSLocalizedString(#"Photo Gallery", #"Photo Gallery");
self.tabBarItem.image = [UIImage imageNamed:#"42-photos.png"];
}
return self;
}
////start of saving////
- (void)applicationDidEnterBackground:(UIApplication*)application {
NSLog(#"Image on didenterbackground: %#", imageView);
self.array = [NSMutableArray arrayWithObject:[NSData dataWithData:UIImagePNGRepresentation(imageView.image)]];
[self.array addObject:[NSData dataWithData:UIImagePNGRepresentation(imageView2.image)]];
[self.array addObject:[NSData dataWithData:UIImagePNGRepresentation(imageView3.image)]];
[self.array addObject:[NSData dataWithData:UIImagePNGRepresentation(imageView4.image)]];
[self.user setObject:self.array forKey:#"images"];
[user synchronize];
}
- (void)viewDidLoad
{
self.user = [NSUserDefaults standardUserDefaults];
NSLog(#"It is %#", self.user);
self.array = [[self.user objectForKey:#"images"]mutableCopy];
imageView.image = [[UIImage alloc] initWithData:[self.array objectAtIndex:0]];
imageView2.image = [[UIImage alloc] initWithData:[self.array objectAtIndex:1]];
imageView3.image = [[UIImage alloc] initWithData:[self.array objectAtIndex:2]];
imageView4.image = [[UIImage alloc] initWithData:[self.array objectAtIndex:3]];
UIApplication *app = [UIApplication sharedApplication];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:app];
backToGalleryButton.hidden = YES;
tapToDeleteLabel.hidden = YES;
deleteButton1.hidden = YES;
[super viewDidLoad];
}
///// shows the hidden and invisible "delete" button over each photo.
- (IBAction)editButtonPressed:(id)sender {
grabButton.hidden = YES;
editButton.hidden = YES;
backToGalleryButton.hidden = NO;
tapToDeleteLabel.hidden = NO;
deleteButton1.hidden = NO;
}
////
// This is when the user taps on the image to delete it.
- (IBAction)deleteButtonPressed:(id)sender {
NSLog(#"Sender is %#", sender);
UIAlertView *deleteAlertView = [[UIAlertView alloc] initWithTitle:#"Delete"
message:#"Are you sure you want to delete this photo?"
delegate:self
cancelButtonTitle:#"No"
otherButtonTitles:#"Yes", nil];
[deleteAlertView show];
int imageIndex = ((UIButton *)sender).tag;
deleteAlertView.tag = imageIndex;
}
- (void)alertView: (UIAlertView *) alertView
clickedButtonAtIndex: (NSInteger) buttonIndex
{
if (buttonIndex != [alertView cancelButtonIndex]) {
NSLog(#"User Clicked Yes. Deleting index %d of %d", alertView.tag, [array count]);
NSLog(#"The tag is %i", alertView.tag);
[self.array removeObjectAtIndex: alertView.tag];
NSLog(#"After deleting item, array count = %d", [array count]);
NSLog(#"Returned view is :%#, in view: %#", [self.view viewWithTag:alertView.tag], self.view);
((UIImageView *)[self.view viewWithTag:alertView.tag]).image =nil;
}
[self.user setObject:self.array forKey:#"images"];
}
Update: I added breakpoints, and discovered that ((UIImageView *)[self.view viewWithTag:alertView.tag]).image =nil; is the line that is causing the crash, I still can't figure out why though.
I don't see setImage: being called in any of that code you pasted into your question, but I can tell you this: UIButton's setImage method actually requires a button state as a second parameter.
e.g.
[UIButton setImage: forState:]
I've linked Apple's documentation for you.
If your calls to setImage: don't have a second parameter, that would explain the "unrecognized selector error" you're seeing.
This is an extension to #MichaelDautermann , who makes a very good point that a UIButton has a setImage:forState: method, but no setImage method. However, you ARE calling set image. Not on a button, but when you say imageView.image and imageView2.image. This invokes the setter method for the image view. I would set a breakpoint (or use NSLog and the %# item) to ensure that imageView is in fact an imageView and not a button. If it somehow changed from under you, this could be causing the issue. Simply set a break point at those two lines and see if you even make it past them.
Additionally, if Xcode isn't popping you over to which line is actually causing the issue, check your crash logs. Symbolicated, the log will give you the line number. Or, a less direct approach would be to set breakpoints at the ends of the methods you provided in your answer, and see how many you get past. Once you crash, you can narrow down which method is causing you grief, and then start setting break points within the method until you get to the line in question.
UPDATE:
You said in the comments that ((UIImageView *)[self.view viewWithTag:alertView.tag]).image =nil is what you have, but self.view is a UIControl. Changing the cast won't affect the result. Is the UIImageView whose image you are trying to delete a subview of a UIControl? If not, you're never going to get the image view back from viewWithTag. Generally, self.view refers to a view controller's view, so if you're getting a UIControl, my assumption is either your whole view is a UIControl, or you're doing this in the wrong class. Which class are you doing this in, and what is it a subclass of? It appears you are doing this in a view controller, but I just want to be sure. And again, in either case (UIControl or UIImageView), neither class responds to setImage.

Adding a UIImage from the UIImagePickerController to a UIButton

I have a UIButton which the user clicks on to bring up a UIImagePickerController. Once this has processed, it returns an edited UIImage to a delegate handler, which then is supposed to populate the UIButton with the new image.
In practise, however, what happens is if the user selects an image from their library, it works fine. But if they take a picture using the camera and edit it, the image doesn't make it to the UIButton. However, if I put the same image into a UIImageView for test purposes, it shows up.
Moreover, this works fine in the Simulator, but doesn't work on the device. Is it some kind of memory issue? Here's my code:
- (IBAction)takePictureButtonTapped
{
UIActionSheet *popupQuery = [[UIActionSheet alloc]
initWithTitle:nil
delegate:self
cancelButtonTitle:#"Cancel"
destructiveButtonTitle:nil
otherButtonTitles:#"Take a Photo", #"Upload from Library", nil];
popupQuery.actionSheetStyle = UIActionSheetStyleBlackOpaque;
[popupQuery showInView:self.view];
[popupQuery release];
}
- (void)actionSheet:(UIActionSheet *)actionSheet
clickedButtonAtIndex:(NSInteger)buttonIndex
{
NSLog(#"actionSheet:clickedButtonAtIndex:%d", buttonIndex);
if(buttonIndex == 2)
{
// cancel
[imagePickerController release];
}
else
{
imagePickerController = [[[UIImagePickerController alloc] init] autorelease];
imagePickerController.delegate = self;
imagePickerController.allowsEditing = YES;
if (buttonIndex == 0)
{
// Take a photo
// Set up the image picker controller and add it to the view
imagePickerController.sourceType = UIImagePickerControllerSourceTypeCamera;
}
else if (buttonIndex == 1)
{
// Upload from Library
imagePickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary] == NO)
imagePickerController.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;
}
[self presentModalViewController:imagePickerController animated:YES];
}
}
- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingImage:(UIImage *)img
editingInfo:(NSDictionary *)editInfo
{
NSLog(#"imagePickerController::didFinishPickingImage:%#", img);
itemImage = img;
[itemImage retain];
[imageButton setBackgroundImage:itemImage forState:UIControlStateNormal];
[[picker parentViewController] dismissModalViewControllerAnimated:YES];
}
I've tried setImage, setBackgroundImage, for ALL states, and none of them work. Yet if I put the same image into a UIImageView, it's fine.
Any ideas?
I discovered that the reason it wasn't working properly was because I was using didFinishPickingImage:editingInfo: which is DEPRECATED. I should've been using imagePickerController:didFinishPickingMediaWithInfo: instead. Now it's working perfectly!
For some reason XCode wasn't warning me of the deprecation.