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. Deleting index %d of %d", alertView.tag, [array count]);
[self.array removeObjectAtIndex: alertView.tag];
NSLog(#"After deleting item, array count = %d", [array count]);
alertView.tag.image = nil;
}
[self.user setObject:self.array forKey:#"images"];
}
The issue here is alertView.tag.image. I have an error stating "Member reference base type NSInteger (aka int) is not a structure or union. This code deletes the image out of the array just fine, but I still need to delete the image from the UI as well. I thought that alertView.tag.image would have done the trick. I have no idea how to do this, I'm still new to Objective-C and the book I read does not cover any of this at all. I was also wondering how I could refresh the UI after deleting the image?
tag is just NSInteger. It doesn have property called image. You should use,
((UIImageView *)[self.view viewWithTag:alertView.tag]).image =nil;
You're trying to access a property that doesn't exist:
alertView.tag.image = nil;
Your alertView is a UIAlertView - and the tag is an integer property, which represents the int value you've assigned to the view (assuming you've done so). tag is just a plain old int - it's a primitive.
What you instead need to do is take the tag value and call viewWithTag on the superview that's holding your images: this will give you a reference to the image view, which you can then removeFromSuperview as required.
How are you adding the image to the UI? Set the image in the UIImageView to another image or remove it from the view.
You cannot add .image after .tag. Tag is an integer instance field used to denote one view from another (among other things). Tag itself, does not have any properties, other than its value.
Related
Ok I have a tableView with a UIActionSheet within it and there is no segue or anything a long those lines. A user has the option to swipe and delete a row or another option to take the user represented by a row a offline again.
When a user swipes they can delete the row.
When a user taps a UIActionsheet pops up with the option to "Take Offline" (destructive button) or cancel.
This is all wrapped in a tableView delegate method:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"%d", [indexPath row]);
Person *person = [people objectAtIndex:indexPath.row];
UIActionSheet *popup = [[UIActionSheet alloc] initWithTitle:[NSString stringWithFormat:#"Need to edit %#'s info?", person.name]
delegate:self
cancelButtonTitle:#"Cancel"
destructiveButtonTitle:#"Take Offline"
otherButtonTitles:nil, nil];
[popup showInView:tableView];
[popup addButtonWithTitle:#"Cancel"];
[people removeObjectAtIndex:[indexPath row]];
[tableView reloadData];
}
I would normally use a UIActionSheetDelegate method to detect the tap of a button.
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex == 1) {
NSLog(#"Button 1 was clicked");
}
}
The problem I'm having is when I tap a row in my table the object is removed from the array and the table is reloaded instantly.
I don't want this to happen until the "Take Offline" button is clicked but can't figure out a way to do this. I've tried a future such as storing objects in instance variables and accessing them from other methods and trying to execute the code out of the tableView method but keep confusing myself.
What is the best way to do this?
Kind regards
I have written a blog post about how to (and why) add block callbacks to alert views, action sheets and animations:
http://blog.innovattic.com/uikitblocks/
Using this approach you don't have to use a delegate and can instead rewrite your tableView:didSelectRowAtIndexPath: method like so:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"%d", [indexPath row]);
Person *person = [people objectAtIndex:indexPath.row];
UIActionSheet *popup = [[UIActionSheet alloc] initWithTitle:[NSString stringWithFormat:#"Need to edit %#'s info?", person.name]
cancelButtonTitle:#"Cancel"
destructiveButtonTitle:#"Take Offline"
otherButtonTitles:nil, nil];
[popup setHandler:^(UIActionSheet *sheet, NSInteger buttonIndex) {
[people removeObjectAtIndex:[indexPath row]];
[tableView reloadData];
} forButtonAtIndex:[popup destructiveButtonIndex]];
[popup showInView:tableView];
}
You can download the source files for this from GitHub:
https://github.com/Innovattic/UIKit-Blocks
Well, you're on the right line with the approach you say you've tried.
You'll need to keep a reference to the selected Person in a property. Declare a property like so:
#property (nonatomic, strong) Person *selectedPerson;
And store the selected person in this property in your didSelectRow... method. Then, use the UIActionSheetDelegate method to determine which button the user clicked. If they clicked 'Take Offline', then remove the person you stored in the selectedPerson property from the people array.
I have an NSDictionary that holds all the data:
One title (not important for this question)
One link (not important for this question)
One array of NSDictionary containing again 1 title and 1 link
I'm displaying this data in a view based table view like this:
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tv
{
if (tv == _downloadTable)
//I use this "if" because I have another tableView that has nothing to do
//with this one
{
return [[myDictionary objectForKey:#"myArray"] count];
}
}
I want 2 columns in this tableView, one to display the title and one with a checkbox, that would do something letting me know which row is checked.
- (NSView *)tableView:(NSTableView *)tv viewForTableColumn :(NSTableColumn *)tableColumn row :(NSInteger)row
{
if (tv == _downloadTable)
{
if (tableColumn == _downloadTableTitleColumn)
{
if ([[[myDictionary objectForKey:#"myArray"]objectAtIndex:row]objectForKey:#"title"])
{
NSString *title = [[[myDictionary objectForKey:#"myArray"]objectAtIndex:row]objectForKey:#"title"];
NSTableCellView *result = [tv makeViewWithIdentifier:tableColumn.identifier owner:self];
result.textField.stringValue = title;
return result;
}
}
if (tableColumn == _downloadTableCheckColumn)
{
NSLog(#"CheckBox"); //I wanted to see exactly when that was called
//But it didn't help me :(
NSButton *button = [[NSButton alloc]init];
[button setButtonType:NSSwitchButton];
[button setTitle:#""];
return button;
}
}
}
Right now when I run it and click on the checkbox it does nothing
(of course because I don't know how to make it do something.
Where should I put the code that should do something?
The main goal is an editable list of downloads, right now the list is displayed, with the checkbox right next to the title at each lines.
I would like to know which checkBox are checked and which are not.
I tried this:
[button setAction:#selector(checkBoxAction:)];
- (void)checkBoxAction: (id)sender
{
NSLog(#"I am button : %# and my state is %ld", sender, (long)[sender state]);
}
But I can't figure out how to get the row of that button, to know which title is associated with this checkBox.
I also tried the setObjectValue method of the tableView without success.
The way I would like it to work is:
I have a "start downloading" button that check if each checkbox is checked or not and launch the next action (downloading) only with the checked row.
I would like to avoid bindings because I plan to make it work on iOS too and I don't want to have different code for iOS.
You can use the NSTableView method -rowForView: to get the row a particular view is in.
In your case you'd have something like this:
- (void)checkBoxAction:(id)sender
{
NSInteger row = [_downloadTable rowForView:sender];
NSLog(#"The button at row %ld was clicked.", row);
}
Here are the docs for NSTableView: https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/NSTableView_Class/Reference/Reference.html
You could try using the button' tag property setting it for each button you place as the number (location) in the tableview. Look here!!!
Detecting which UIButton was pressed in a UITableView
[EDIT1]
If people actually decided to read the linked post you would realize that the answer is actually there.
Try adding:
[button setTag:row];
[button addTarget:self action:#selector(checkBoxAction:) forControlEvents:UIControlEventTouchUpInside];
inside the else of your viewForTableColumn routine:
In your checkBoxAction routine:
- (void)checkBoxAction: (id)sender{
NSLog(#"I am button : %# and my state is %#", sender.tag, [sender state]);
}
I also think that once you begin digging further into your code, you are going to want to start using the auto-dequeuing capability of the TableViewCell objects. I believe that you are going to find yourself in a memory alloc/dealloc problem.
I'm trying to reproduce a Similar Media Picker that is like the one in Pages. Within a UIPopoverController There is a UISegmentedControl that selects different media Types. One of the SegmentedControls I have is Labeled Images. I want to be able to select that Segment and have the view below present the ImagePicker.
I'm Close. I have a few issues. When presenting the VC, I get:
I get the Following in the Debugger:
UIStatusBarStyleBlackTranslucent is not available on this device. Not sure where that is coming from. I've tried with and without:
imagePicker.modalInPopover = YES;
imagePicker.modalPresentationStyle = UIModalPresentationCurrentContext;
Though I still cannot get it to work Right. It Presents just fine. I see the UISegmentedControl I see my other Media Pages, I click the Image segment and I see the ImagePicker, Title is 'Photos' Has a Cancel Button I need to get rid of, and shows the two Albums I have on the device.
If I tap anywhere in the TableView (on an album or not), the two Albums go away. The NavBar and Cancel button are still there, though no Albums anymore. Tapping an Album Highlights the Row, though does not show the Images within the Album.
The other odd part of my code is that the Delegate for the Image Picker is the VC that Presented the UIPopoverController. Not sure if that plays into it. When I do hit the Cancel Button, I get:
-[PLUILibraryViewController performSelector:withObject:withObject:]: message sent to deallocated instance
Here is my Code to present the Picker.
- (void) setupImagePicker {
IoScreenEditorViewController * ioScreenEditorViewController = (IoScreenEditorViewController *)[UIAppDelegate.ioMainViewController currentViewController];
ioScreenEditorViewController.elementSelectedFromList = [elementsForPage objectAtIndex:0];
// Show an image picker to allow the user to choose a new photo.
UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
imagePicker.delegate = ioScreenEditorViewController;
imagePicker.allowsEditing = NO;
NSArray * ourMediaTypes = [[NSArray alloc] initWithObjects: (NSString *) kUTTypeImage, nil];
[imagePicker setMediaTypes: ourMediaTypes];
[ourMediaTypes release];
if ([UIImagePickerController isSourceTypeAvailable: UIImagePickerControllerSourceTypeCamera]) {
// imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
} else {
imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
}
[imagePicker.view setFrame:CGRectMake(0,0, 340, 500)]; // just for testing
//imagePicker.modalInPopover = YES;
//imagePicker.modalPresentationStyle = UIModalPresentationCurrentContext;
[self.view addSubview:imagePicker.view];
[imagePicker release];
}
I changed the class type that the Picker was being called from to a UIViewController and then altered the presentation code to look like this:
imagePicker.modalInPopover = YES;
imagePicker.modalPresentationStyle = UIModalPresentationCurrentContext;
[self presentViewController:imagePicker animated:NO completion:^{ }];
I still get the Cancel Button, though the Image Picker does seem to be working like it should.
Although I was also as of yet unable to hide the Cancel button of the picker, I would suggest you revise how you are presenting it.
As an effort to get help from the community of iOS developers about this, I've made a sample project, please go and download it. In the project you can see that I am using view controller containment in order to get the picker on screen.
This may perhaps be a better way for you do do this too?
https://bitbucket.org/danielphillips/image-picker-demo
I'm trying to display a UIActionSheet from my iPad. Here's the code that I'm using:
-(void) presentMenu {
UIActionSheet *popupMenu = [[UIActionSheet alloc] initWithTitle:#"Menu" delegate:self cancelButtonTitle:#"Cancel" destructiveButtonTitle:nil otherButtonTitles:nil];
for (NSString *option in _menuItems) {
[popupMenu addButtonWithTitle:option];
}
popupMenu.actionSheetStyle = UIActionSheetStyleBlackOpaque;
if(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
[popupMenu showFromTabBar:_appDelegate.tabBar.tabBar];
}
else if(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[popupMenu showFromBarButtonItem:self.navigationItem.rightBarButtonItem animated:YES];
}
[popupMenu release];
return;
}
The iPhone version of the program displays all the buttons in _menuItems, but the iPad version just ignores the last item from that array. Does anyone know why this might be happening?
Thanks,
Teja.
Found the answer as soon as I typed out this post. Somehow removing the "Cancel" button causes both the buttons to come up. Weird.
EDIT: Although, this is really annoying because all my button indices change between the iPhone and the iPad versions (The iPhone still needs the cancel button). How do I handle this?
I think what iOS is doing is it's expecting the last button to be the cancel button (regardless of whether it is or not) and is removing it, but maybe only for iPads. This is probably because a user can tap outside the action sheet to dismiss it. The problem I have with Apple's design choice is that it may not always be evident that the dialog can or should be dismissed in that way.
For example, I am showing my action sheet by calling [actionSheet showInView:self.view]; This causes the entire view to be grayed with the action sheet displaying in the middle of the device. Users are going to--rightly, in my opinion--assume that they have to choose one of the buttons.
I understand there are other action sheet display mechanisms--like the one that displays it as a bubble attached to a bar button item--where a cancel button is obviously redundant. It would be nice if Apple allowed for more flexibility here. For my app, I am probably going to have to add a dummy button to the end of the array I'm passing into my custom constructor, knowing that iOS will hide it. If the behavior changes in a future release of iOS... well, I'll just have to address it at that time.
In your case, I recommend not using the constructor that takes cancelButtonTitle and destructiveButtonTitle. Instead, subclass UIActionSheet and add buttons manually using the method above. Then, set cancelButtonIndex and destructiveButtonIndex to the desired indices. Remember that you don't have to set those two properties; they default to -1 (no button). Also, remember to abide by the HIG regarding the position of your buttons.
Here's one of my subclass' constructors (edited for brevity), just to give you an idea:
- (instancetype)initWithTitle:(NSString *)title
buttonTitles:(NSArray *)buttonTitles
cancelButtonIndex:(NSInteger)cancelButtonIndex
destructiveButtonIndex:(NSInteger)destructiveButtonIndex
{
self = [super initWithTitle:title delegate:nil cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles:nil];
if (self)
{
if (buttonTitles)
{
[buttonTitles enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop)
{
[self addButtonWithTitle:obj];
}];
}
self.cancelButtonIndex = cancelButtonIndex;
self.destructiveButtonIndex = destructiveButtonIndex;
if (self.cancelButtonIndex > -1)
{
[self addButtonWithTitle:#""];
}
}
return self;
}
[EDIT] Hmm. Perhaps this question should be titled "what is the default user-input dialog view called in CocoaTouch?" I realize that I can create an entire view that is exactly what I want, and wrap it in a view controller and presentModalView -- but I was sort of hoping that there was a standard, normal user-input "dialog" view that came-with Cocoa-touch. "Enter your name", "enter text to search", etc., are VERY common things!
Anyway... here's the question as I originally asked it:
This code:
UIAlertView* find = [[UIAlertView alloc] init];
[find setDelegate:self];
[find setTitle:#"Find"];
[find addButtonWithTitle:#"Cancel"];
[find addButtonWithTitle:#"Find & Bring"];
[find addButtonWithTitle:#"Find & Go"];
[find addButtonWithTitle:#"Go To Next"];
[find addSubview:_findText];
CGRect frm = find.frame;
int height = frm.size.height + _findText.frame.size.height + 100; // note how even 100 has no effect.
[find setFrame:CGRectMake(frm.origin.x, frm.origin.y, frm.size.width, height)];
[find setNeedsLayout];
[find show];
[find release];
Produces this Alert view:
Find Alert http://www.publicplayground.com/IMGs/Misc/FindAlert.png
(I started with the code from this question by emi1Faber, and it works as advertised; however, as I state in my comment, the cancel button overlays the text field.)
How do I reshuffle everything to make the text field fit properly? [findAlert setNeedsLayout] doesn't seem to do anything, even after I [findAlert setFrame:tallerFrame]. Hints?
Thanks!
The simplest (and most proper way) to move the text view down is to add a message
[find setMessage:#"\n"];
Also, the reason your frame isn't taking effect is that -show sets the frame and creates the view hierarchy before starting the animation. You should also make the text view the first responder so the keyboard pops up.
Full example:
// Create Alert
UIAlertView* av = [UIAlertView new];
av.title = #"Find";
// Add Buttons
[av addButtonWithTitle:#"Cancel"];
[av addButtonWithTitle:#"Find & Bring"];
[av addButtonWithTitle:#"Find & Go"];
[av addButtonWithTitle:#"Go to Next"];
// Make Space for Text View
av.message = #"\n";
// Have Alert View create its view heirarchy, set its frame and begin bounce animation
[av show];
// Adjust the frame
CGRect frame = av.frame;
frame.origin.y -= 100.0f;
av.frame = frame;
// Add Text Field
UITextField* text = [[UITextField alloc] initWithFrame:CGRectMake(20.0, 45.0, 245.0, 25.0)];
text.borderStyle = UITextBorderStyleRoundedRect;
[av addSubview:text];
[text becomeFirstResponder];
Note: You can also modify the subviews of UIAlertView, but since Apple has already changed the UIAlertView layout once you should check their class descriptions and frames against known values before setting new ones. You can even get something like this:
(source: booleanmagic.com)
Even if you can get this working it's not going to be very iPhone-y. The UIAlertView really is not designed for user input like this. If you look in all the Apple apps you'll see that they use a new view that displayed using the presentModalViewController: method of UIViewController.
Edit: This advice is no longer as true as it was when I wrote it. Apple have increasingly used alert views as text entry boxes and iOS5 even includes native support without having to mess around with views (check out the alertViewStyle property).
I think maybe if you need to have four buttons then using a custom UIViewController is probably still the right way to go. But if you just want to enter a password with OK/Cancel buttons then it's fine.
Zoul proposed the best method, to capture user input just do:
a) Add the UITextFieldDelegate protocol to your class.
b) Do something like
UIAlertView *insertScore = [UIAlertView new];
[insertScore setDelegate:self];
[insertScore setTitle:#"New Title!"];
[insertScore addButtonWithTitle:#"Cancel"];
[insertScore addButtonWithTitle:#"Ok"];
insertScore.message = #"\n";
[insertScore addTextFieldWithValue:#"Input" label:#"player"];
[[insertScore textField] setDelegate:self];
[insertScore show];
[insertScore release];
c) The crucial part was to set the delegate of the textField to self, then to access data you can simply:
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
NSLog(#"%#",[[alertView textField] text]);
}
Hope this helps someone, since I had to think a bit to get it right.
Most probably You would want to look into the addTextFieldWithValue method of the UIAlertView? Add the following code somewhere at the top of Your class:
#interface UIAlertView ()
- (void) addTextFieldWithValue: (NSString*) val label: (NSString*) label;
- (UITextField*) textField;
#end
It’s not official, but IMHO it’s not getting You rejected from the App store and it’s much better solution than hacking the textfield into the dialog Yourself.
Explains how to set the number of columns, have not tested it.
http://iloveco.de/uikit-alert-types/
However there is a private method,
setNumberOfRows:(int)n that will allow
you to set a maximum number of rows to
display the buttons in. To use this
method we need to add our own
additions to the UIAlertView class. We
do this by adding an #interface for
UIAlertView in our .m file.
// extend the UIAlertView class to remove the warning caused
// by calling setNumberOfRows.
#interface UIAlertView (extended)
- (void) setNumberOfRows:(int)num;
#end
This will allow us to call the method without the compiler throwing us a warning.
[myAlert setNumberOfRows:2];
Try putting in some (\n)s after the title in the UIAlertView initialization. That will push down the buttons. And I agree with Stephen here. There are chances that Apple might reject an app if it uses controls in a way they shouldn't be. (there's some clause in the Human Interface Guidelines about that!)
This simpler method works for me:
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"UIAlertView"
message:#"<Alert message>" delegate:self cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert addTextFieldWithValue:#"" label:#"Text Field"];
Hope that helps. Oh if you needed multiple button rows then it's:
[alert setNumberOfRows:3];
Cheers
https://github.com/TomSwift/TSAlertView
This library actually creates the control from scratch rather than attempting to hack UIAlertView, which is generally a Bad Plan (TM)