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;
}
Related
Using Xcode I have View A that navigates to View B.
Upon pressing the Back UIBarButtonItem, I'm trying present the user with a UIActionSheet to confirm navigation to move back to View A.
What do I need to do in code to stop the view from navigating back and then (depending on user input) move back or stay on the current screen?
add a backbutton programmatically.
eg.
UIButton *backBtn= [[UIButton alloc] initWithFrame:CGRectMake(0,0,54,30)];
[backBtn addTarget:self action:#selector(backButtonPressed:)forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem *backBarButton = [[UIBarButtonItem alloc] initWithCustomView:backBtn];
[backBtn release];
[[self navigationItem] setLeftBarButtonItem:backBarButton];
[backBarButton release];
//backButtonPressed is the selector for backBtn
Then present you ActionSheet from that selector and based on user either navigate to previous viewController or dont.
To navigate to previous page, use popViewMethod.
`
You should not present UIActionSheet for every other action.It would be better to use UIAlertView for this purpose. According to Apple UIActionsheet Guidelines :-
Provide alternate ways a task can be completed. An action sheet allows you to provide a range of choices that make sense in the context of the current task, without giving these choices a permanent place in the user interface.
Get confirmation before completing a potentially dangerous task. An action sheet prompts users to think about the potentially dangerous effects of the step they’re about to take and gives them some alternatives. This type of communication is particularly important on iOS-based devices because sometimes users tap controls without meaning to.
for UIAlertView :-
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Alert View"
message:#"Do You want to go back to previous screen?"
delegate:self
cancelButtonTitle:#"NO"
otherButtonTitles:#"YES",nil];
[alertView show];
[alertView release];
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 0) {
NSLog(#"THE 'NO' BUTTON WAS PRESSED");
}
if (buttonIndex == 1) {
NSLog(#"THE 'YES' BUTTON WAS PRESSED");
}
}
Implement this on action of back button of UINavigationController.According to the buttons pressed "YES" or "NO" , you can allow navigation.Also conform to UIAlerrtVIewDelegate protocol.
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.
I have some code here for an iOS app I am developing, and for some reason what seems like a quick and simple task takes my iPhone 4S a full second or more to do, every time.
The context is this... I have a 2 button ActionSheet popup, and if the user taps either one of the buttons, the app seems to stall for about a second. Here's the code:
-(void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 0)
{
UIAlertView* newTimerAlertView = [[[UIAlertView alloc] initWithTitle:#"Create New Timer"
message:#"Enter a name for your new indicator"
delegate:self
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"Create", nil] autorelease];
newTimerAlertView.tag = kNewTimer;
newTimerAlertView.alertViewStyle = UIAlertViewStylePlainTextInput;
[newTimerAlertView show];
}
else if (buttonIndex == 1)
{
NSLog(#"ActionSheet button 2 tapped");
UIAlertView* newTallyAlertView = [[[UIAlertView alloc] initWithTitle:#"Create New Tally"
message:#"Enter a name for your new indicator"
delegate:self
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"Create", nil] autorelease];
newTallyAlertView.tag = kNewTally;
newTallyAlertView.alertViewStyle = UIAlertViewStylePlainTextInput;
[newTallyAlertView show];
NSLog(#"end");
}
}
Focusing on the second button for simplicity (although the first button behaves the same way), the log looks like this:
2012-01-25 20:35:46.330 ...[177:707] ActionSheet button 2 tapped
2012-01-25 20:35:47.194 ...[177:707] end
2012-01-25 20:35:56.154 ...[177:707] ActionSheet button 2 tapped
2012-01-25 20:35:56.180 ...[177:707] end
Notice that the first time I try it, over a second passes before the code snippet finishes executing, but the second time (and all subsequent times) the code only takes 30 or so milliseconds.
Is there something wrong with the code? or do I need to simply make up for the delay with a progress view?
Thanks!
EDIT: This only occurs on devices when running the app from Xcode... maybe it's got to do with the debugger?
I now that if the alert is not on the main thread they may be delayed
Xcode doesn't slow down alert views in any situation I have encountered.
Your code looks pretty plain vanilla, so I suspect there's something going on outside the snippet you posted.
I see that you reference timers... is is possible this is a manifestation of something else, like a timer callback, going wild on the main thread? Try using the Time Profiler or the System Trace instruments to figure out what's blocking (something certainly is).
How I can deal with UIActionSheet and three20?
I am following this tutorial.
I know about displaying a single photo by clicking on one from the thumbnail viewer is provided to you for free by Three20. In addition, the library also provides all of the native functions such as pinch-to-zoom, swiping to navigate and tapping to hide/show the navigation arrows and back button.
I want to add a button that if clicked by the user, would display a UIActionSheet with options for sharing the photo by mail or MMS and save( using UIImageWriteToSavedPhotosAlbum )and the user would choose one of them.
I see a lot of tutorials about this but I need to do this by my self.
in .h file add
UIActionSheet *actionViewPopup;
in .m file
actionViewPopup = [[UIActionSheet alloc] initWithTitle:nil
delegate:self
cancelButtonTitle:#"Cancel"
destructiveButtonTitle:nil
otherButtonTitles:#"email",#"sms",nil];
[actionViewPopup showInView:self.parentViewController.tabBarController.view];
CODE FOR HANDLING CLICK OF ACTIONSHEET
-(void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
//THIS METHOD SHOWS WHICH BUTTON FROM ACTION SHEET IS PRESSED
if (buttonIndex == 0)
{
NSLog(#"email clicked");
//do code for email (mail composer)
}
if (buttonIndex == 1)
{
NSLog(#"sms clicked");
//do code for sms
}
}
[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)