UIActionSheet does not show -- the screen just gets darker - iphone

I have an iPhone application which is based on the "Window based application" template and which uses a main view with some embedded subviews.
For some action I need a confirmation by the user. Therefore, I create an UIActionSheet and ask the user for feedback.
The problem is, that the action sheet does not show at all. Instead, the screen gets darker. The sheet and the requested buttons do not show. After that, the application hangs. The darkening of the screen is a normal behavior as part of the animation which normally shows the action sheet.
Curiously, the same code works fine, if invoked in the viewDidLoad method. It does not work if invoked in the buttonPressed method which starts the action requiring the confirmation.
- (void) trashButtonPressed {
// This method is called in the button handler and (for testing
// purposes) in the viewDidLoad method.
NSLog( #"trashButtonPressed" );
UIActionSheet* actionSheet =
[[UIActionSheet alloc] initWithTitle: #"Test"
delegate: self
cancelButtonTitle: #"Cancel"
destructiveButtonTitle: #"Delete Sheet"
otherButtonTitles: nil];
[actionSheet showInView: self.view];
[actionSheet release];
}
- (void) willPresentActionSheet:(UIActionSheet *) actionSheet {
NSLog( #"willPresentActionSheet" );
}
- (void) didPresentActionSheet:(UIActionSheet *) actionSheet {
NSLog( #"didPresentActionSheet" );
}
- (void) actionSheet:(UIActionSheet *)actionSheet
didDismissWithButtonIndex:(NSInteger)buttonIndex {
NSLog( #"actionSheet:didDismissWithButtonIndex" );
}
As you can see, I have added some logging messages to the protocol handlers of the UIActionSheetDelegateProtocol. The "will present" and "did present" methods get called as expected, but the sheet does not show.
Does anybody know, what's wrong here?

I wonder if [actionSheet showInView: self.view]; is enough to have the actionSheet have itself retained by self.view. (edit: retain count jumps from 1 to 4 so not a problem here)
Have you checked the dimensions of your view? The sheet is positioned within the view, but if self.view would refer to a big scrollview, you might just have a sheet below the surface. In short, are you sure that self.view.frame and self.view.bounds have the same values in the two situations you are referring to? Is it the same view (when just NSLog(#"%x",self.view)-ing it's address)?
edit to clarify: do
NSLog(#"%f %f %f %f",
self.view.frame.origin.x,self.view.frame.origin.y,
self.view.frame.size.width,self.view.frame.size.height);
and please tell what you see on the console. I get your "screen just gets darker" if I set either a 0,0,0,0 frame or a 0,0,320,800 frame, so this might be it...

Actually, I think you've hit the same issues I've seen.
In iOS 8 & 9, the UIActionSheet does get displayed... oh... but behind the onscreen keyboard, so you can't see it. You just see the rest of the screen going darker.
This, apparently, is Apple's latest UI improvement idea, to keep the screen uncluttered. ;-)
The simple solution is to add one line of code before displaying your UIActionSheet:
[self.view endEditing:true];
This dismisses the onscreen keyboard, making your beautiful action sheet visible again.
Alternatively, I have documented here how to replace your UIActionSheet with UIAlertController.

Related

UIToolbar items disappear - Weird bug in ios6

This is the current setup.
I have the navigationController's toolbar with 5 buttons, and tapping on them hides the toolbar for 2 seconds, and then shows the toolbar again (except the 5th button - which brings up an actionsheet with buttons (ACTION & CANCEL)).
On tapping on the 1-4 buttons, I do a self.navigationController.toolbarHidden = YES; and after exactly 2 seconds, I set the self.navigationController.toolbarHidden = NO; and this brings back the toolbar, and everything's fine.
On tapping the 5th button, which brings up action sheet.
If i tap on CANCEL actionsheet => actionSheet dismissed => Toolbar is fine.
If I tap on ACTION button I do a self.navigationController.toolbarHidden = YES; and after 2 seconds... self.navigationController.toolbarHidden = NO;
but now... The toolbar buttons are GONE.
Further investigating...
I can see the the toolbarButtons seem to have their alpha values set to 0.
I have no idea why the toolbar items' alpha are set to value = 0 after actionsheet operation.
Can anyone tell me the root cause for this?
Have you tried setting the toolbar items array to nil? I had this same problem and it turned out that putting a check around when you set the toolbar's items seemed to work:
if ([self.navigationController.toolbar.items count] > 0) {
[self.navigationController.toolbar setItems:nil];
}
[self.navigationController.toolbar setItems:toolbarItems]; //toolbarItems is your array of UIBarButtonItems.
I managed to fix the issue in a different way. I hide the toolbar when the action sheet comes up, and after the buttonAction(), I essentially show the toolbar again.
This solves the problem where the toolbarItems disappear.
But the reason as to why the toolbarItems disappear and set alpha=0 is still a mystery for me. If anyone finds out the reason, please let me know :)
I had the same issue and reproduced it in one of the samples. It appears to be a bug in iOS6 when setting up toolbar items manually in loadView / viewDidLoad, then later calling an ActionSheet.
The code below is a workaround it -
-(void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex
{
NSArray* items = self.toolbarItems;
[self setToolbarItems:nil];
[self setToolbarItems:items animated:NO];
}
I solve it by moving action code to separate method and then calling it through sending message performSelector:withObject:afterDelay: with 0.25f second delay
Example:
-(void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex == 0) {
[self performSelector:#selector(logout) withObject:nil afterDelay:0.25f];
}
}
I don't know if it's the case, I found out that the disappeared items were actually in the toolbar, but placed over the bottom of the view. Maybe resetting them on certain circumstances may cause autolayout issues.
I fixed it by calling the setNeedLayout method on the viewcontroller's view (not the navigationControllers')
self.toolbarItems = toolButtons;
[self.view setNeedsLayout];

UIPopoverController memory question

I'm creating a UIPopoverController and setting "Editor1" as the content view controller.
When the caller receives the didDismissPopover I'm releasing the UIPopoverController.
This is the code:
- (IBAction)open1:(id)sender {
Editor1 *editor = [[Editor1 alloc] initWithNibName:#"Editor1" bundle:nil];
_popoverController = [[UIPopoverController alloc] initWithContentViewController:editor];
_popoverController.delegate = self;
[editor release];
[self.popOverController presentPopoverFromRect:self.open1Button.bounds inView:self.open1Button permittedArrowDirections:UIPopoverArrowDirectionAny animated:NO];
}
- (BOOL)popoverControllerShouldDismissPopover:(UIPopoverController *)popoverController{
NSLog(#"popoverControllerShouldDismissPopover");
return YES;
}
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController{
NSLog(#"popvoerControllerDidDismissPopover");
[_popoverController release];
}
In my editor I have a UITextField where the user changes text and I save it when I get the message "editingDidEnd"
- (IBAction)editingDidEnd:(id)sender {
NSLog(#"Editing did End");
// SAVE PROCEDURE
}
My question regards the order in which the methods get called.
The order is:
2011-09-07 12:35:21.628 iosTest[1967:b603] popoverControllerShouldDismissPopover
2011-09-07 12:35:21.629 iosTest[1967:b603] popvoerControllerDidDismissPopover
2011-09-07 12:35:21.983 iosTest[1967:b603] Editing did End
2011-09-07 12:35:21.985 iosTest[1967:b603] viewWill Disappear
As you can see the popoverControllerDidDismissPopover gets called before editingDidEnd:, so this means I'm releasing the popover before I do my save procedure. This could bring me a crash problem.
Also, in my save procedure I need to ask the user for confirmation in some cases. I'm using a UIAlertView for this.
Do you have any recommendations?
Usually views are well-behaved and don't send events after they're off-screen. You can check for potential problems by enabling zombies (set the environment variable NSZombieEnabled=YES).
If there is a crash, the correct place to fix it is in -[Editor1 dealloc] (and possibly -viewDidUnload): just do textField.delegate = nil and you should stop receiving callbacks. This is not usually necessary except for web views and scroll views where it seems to be problematic (the scroll animation continues even if the VC is off-screen).
In your case, you can probably make saving happen in -popoverControllerShouldDismissPopover:, returning NO if you need to display a UIAlertView (and dismissing the popover when the button is pressed).
it seems, that _popoverController is the instance-property. in this case you can release it in viewDidUnload method of parent-controller.
Why don't you use UITextFieldDelegate protocol? Usage:
aTextField.delegate = self;
(...)
- (void)textFieldDidEndEditing:(UITextField *)textField {
NSLog(#"Editing did End");
// SAVE PROCEDURE
}
Read the documentation for more info.

UIActionSheet in Landscape has incorrect buttonIndicies

I have an action sheet that is causing me grief on the iphone in Landscape orientation. Everything displays just fine, but in Landscape, the first real button has the same index as the cancel button and so the logic doesn't work.
I've tried creating the actionSheet using initWithTitle: delegate: cancelButtonTitle: destructiveButtonTitle: otherButtonTitles: but that was just the same, my current code is as follows;
UIActionSheet* actionMenu = [[UIActionSheet alloc] init];
actionMenu.delegate = self;
actionMenu.title = folderentry.Name;
actionMenu.cancelButtonIndex = 0;
[actionMenu addButtonWithTitle:NSLocalizedString(#"str.menu.cancel",nil)];
[self addActiveButtons:actionMenu forEntry:folderentry];
[actionMenu showInView:[self.navigationController view]];
[actionMenu release];
The addActiveButtons method basically configures which buttons to add which it does using code like this;
[menu addButtonWithTitle:NSLocalizedString(#"str.menu.sendbyemail",nil)];
There are perhaps 6 buttons at times so in landscape mode the actionSheet gets displayed like this;
My delegate responds like this;
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
NSLog(#"Cancel Button Index is : %d",actionSheet.cancelButtonIndex);
NSLog(#"Button clicked was for index : %d",buttonIndex);
NSString *command = [actionSheet buttonTitleAtIndex:buttonIndex];
DLog(#"COMMAND IS: %# for index: %d",command,buttonIndex);
if ([command isEqualToString:NSLocalizedString(#"str.menu.sendbyemail",nil)]) {
// Do stuff here
}
if ( ... similar blocks ... ) { }
}
In the example shown, I am finding that cancelButtonIndex is 0 as expected, but so is the button index for the first other button! This means if I click on the second (Save to Photos) button for example, my debug output looks like this;
Cancel Button Index is : 0
Button clicked was for index : 1
COMMAND IS: Send by Email for index: 1
I've tried various permutations and am now tearing my hair out wondering what I'm missing. I've had a good search around but the other problems people seem to be having are display issues, rather than functionality ones.
Can anyone see where I've gone wrong?
PS. I know this isn't the greatest UI experience, but I figure that most users will actually be in portrait most of the time or using the iPad version of the app so I'm prepared to accept the actionsheet default behaviour for landscape assuming I can get it to actually work!
OK, fixed it by counting how many buttons I was adding and then adding the cancel button as the last option, so my code looks like this;
int added = [self addActiveButtons:actionMenu forEntry:folderentry];
[actionMenu addButtonWithTitle:NSLocalizedString(#"str.menu.cancel",nil)];
actionMenu.cancelButtonIndex = added;
Hope that helps someone else struggling witht the same issue!
I ran into the same issue even though I already was including the Cancel Button as the last one in the action sheet and setting its index accordingly. My problems had to do with the 'Destructive' button. After some investigation, here is my take on the problem:
After N buttons have been added to the actionsheet, it switches it's layout to put the Destructive button at the top and the Cancel button at the button. In between is a scrollable view that includes all of the other buttons. Other sources indicate that this is a a table view.
For the iPhone, N is 7 for Portrait orientation and 5 for Landscape orientation. Those numbers are for all buttons including Cancel and Destructive.
It does not matter where in the action sheet you had originally put the Cancel and Destructive buttons within the action sheet. Once the limit has been reached, the Destructive button is moved to the top and the Cancel is moved to the bottom.
The problem is that the indices are not adjusted accordingly. So, if you did not initially add the Cancel as the last button and the Destructive as the first, the wrong index will be reported in actionSheet:clickedButtonAtIndex: as the initial report stated.
So, if you are going to have more than N buttons in your action sheet you MUST add the Destructive button to the actionSheet as the first button to the action sheet. You MUST add the Cancel button as the last button added to the action sheet. When initially constructing the sheet just leave both as nil, as described in another answer.
I had the same problem. To fix it, I just create an actionSheet with nil for all the buttons, and added buttons manually afterwards. Lastly, in the handler, ignore the firstOtherButtonIndex because it will be wrong (even if you set it ahead of time). Instead, assume that it is 1 because index 0 is the cancel button in this example. Here's the code:
NSArray *items = [NSArray arrayWithObjects:#"one", #"two", #"three", nil];
UIActionSheet* actionSheet = [[[UIActionSheet alloc] initWithTitle:#"Title" delegate:self cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles:nil] autorelease];
[actionSheet addButtonWithTitle:#"Cancel"];
for (NSString *title in items) {
[actionSheet addButtonWithTitle:title];
}
[actionSheet addButtonWithTitle:#"Destroy"];
// set these if you like, but don't bother setting firstOtherButtonIndex.
actionSheet.cancelButtonIndex = 0;
actionSheet.destructiveButtonIndex = [items count]+1;
Also, don't forget to show this from a tab view if you're on an iPhone because the tab bar steals touch events and prevents the lower button from being hit.
My solution is to initialize like this specifying only the destructiveButtonTitle...
UIActionSheet * as =[[[UIActionSheet alloc] initWithTitle:nil
delegate:self
cancelButtonTitle:nil
destructiveButtonTitle:#"Cancel"
otherButtonTitles:nil] autorelease];
[as addButtonWithTitle:#"Button 1"];
[as addButtonWithTitle:#"Button 2"];
That way you get the Cancel button at index 0 always and your own buttons begin at index 1 even when there is a scroll view.

NSRunLoop timing issue

Maybe there is a better way, but I want to pop a choice list when a user taps a button in a UIalertView. I would like this list to pop while the alert view is still visible and have everything close when the user taps an item in the choice list.
I thought I could do it by adding the list as a subview in the UIAlertView and keep the UIalertView displayed with an NSRunLoop in a while loop that pops with a flag set by the choice list. I cannot get this to work, however, because the flag does not get set before the while loop drops back into the NSRunLoop. A second tap will get it to drop out of the while loop but that is not what I want.
- (void)alertView:(UIAlertView *)alertView willDismissWithButtonIndex:(NSInteger)buttonIndex{
CGRect popUpPickerFrame = alertView.frame;
PopUpPicker *popUpPicker = [[PopUpPicker alloc] initWithFrame:CGRectMake(popUpPickerFrame.origin.x +150,popUpPickerFrame.origin.y-50,115,250)];
popUpPicker.delegate = self;
popUpPicker.aList = [NSArray arrayWithObjects:#"General Plan", #"Light Plan", #"Melatonin Plan", #"Bed Times", #"Done", nil];
popUpPicker.tag = 10;
[alertView addSubview:popUpPicker];
while (popUpPicker.tag == 10) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]];
}
[popUpPicker release];
}
I am setting the popUpPicker.tag to the row the user taps in the tableView:didSelectRowAtIndexPath: method of the list which then calls the lists delegate method.
I can get the popup list to work fine but only after the UIAlertView closes.
Thanks for any help.
John
Your workflow is not applicable to concept of UIAlertView. It is not designed to provide choices in the list after you press some button. Somebody on WWDC 2011 said "Don't fight the framework." This advice is just for you. Avoid alerts except they are really needed, consider using action sheets for your task, or implement the workflow in ViewController.

disable Alert view button

I have an alert view for twitter posting.
Alert view has 2 button and a textfield
send and cancel
I want to disable send button, until user fills the message box(i.e textfield).
like,empty field kind of validation.
How can I disable send button?
I had a similar requirement and was able to do this without resorting to anything explicitly prohibited by Apple (ie, the use of private classes or API's). In the example below, I find and then disable the "Recover" button.
Note #1 -- The placement of "[alert Show]" is important. It (apparently) lays out the views, so must be done before attempting to look through the view hierarchy.
Note #2 -- the "contains:" method is one I defined that does an NSString case-insensitive substring search. Use rangeOfString perhaps in your code.
UIAlertView* alert = [[UIAlertView alloc] initWithTitle:#"Application Warning"
message:#"What should I do with the file?"
delegate:self
cancelButtonTitle:#"Ignore"
otherButtonTitles:#"Remove", #"Recover", nil];
[alert show];
// try to find and disable "Recover" button
for(UIView *aView in alert.subviews)
{
if ([[[aView class] description] contains:#"Button"])
{
UIButton *aButton = (UIButton *)aView;
if ([aButton.titleLabel.text contains:#"Recover"])
{
aButton.enabled = NO;
}
}
}
This is not possible with the current SDK. You will have to create a custom view to take the user's input. The fact you are adding a textfield to the UIAlertView is itself unsupported and could break in any future SDK anyway.
I would suggest you create a custom view and if you still want it to look like a UIAlertView you can do this with appropriate images and custom buttons.