iOS: GestureRecognizer added to a UIAlertView does not call its action - ios

I have an UIAlertView that is dismissed by a timer after a few seconds. Some users want to dismiss the alert view earlier simply by tapping the alert view itself.
I thus tried to add a single tap gesture recognizer to the alertView, but its action was not called. I then read on SO that the alert view must be in a view hierarchy before the gesture recognizer can be added. I am not sure about this but anyway I thus moved adding of the gesture recognizer to the alert view delegate method didPresentAlertView:, but the action is still not called.
Here is my code. Any help is appreciated.
The initialization of the alert view:
alertView = [[UIAlertView alloc] initWithTitle:#"title" message:#"message" delegate:self cancelButtonTitle:nil otherButtonTitles:nil];
}
[alertView performSelectorOnMainThread:#selector(show) withObject:self waitUntilDone:NO];
[self performSelector:#selector(dismissAlertAfterDelay:) withObject:alertView afterDelay:4.0];
The delegate method where the gesture recognizer is added:
- (void)didPresentAlertView:(UIAlertView *)alertView{
alertView.userInteractionEnabled = YES;
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget: self action:#selector(dismissAlert:)];
singleTap.numberOfTapsRequired = 1;
[alertView addGestureRecognizer:singleTap];
}
and the action method that is NOT called:
-(void)dismissAlert:(UITapGestureRecognizer *)sender{
UIAlertView *alertView = (UIAlertView *)sender.view;
[alertView removeFromSuperview];
}
I know that an alert view is displayed in a separate window, but I believe anyway that a gesture recognizer added to it should call the action method when user interaction is enabled for the alert view.
Any suggestions?

UIAlertView is, despite its name ending in View, a model class in iOS 7. It never gets added to the view hierarchy. That's why adding subviews to it doesn't work either: they are added, but their parent isn't in the view hierarchy. The view hierarchy that's presented when you show an alert is private and is not to be messed with.
I suggest following the HIG by just using a dismiss button. If you really, really want the entire alert to be able to be tapped, look at SDCAlertView. It's a UIAlertView clone I wrote that does act like an actual view.

Related

UIAlertview button action to change to another viewcontroller?

I am just getting into Xcode programming, and I need code so that when I click a button, it is a confirmation to take me into another ViewController.
I have an X button on the page, and when clicked I need a UIAlertview to pop up saying 'cancel' and 'next', and when next is pressed, i want it to change to another ViewController. These cancel and next buttons need to be side by side.
First thing you need to do is make sure the view with a button is a delegate of UIAlertView.
#interface ViewController() <UIAlertViewDelegate>
Then you need to add a touch up inside action to the button in your view. When the button is pressed, it creates the alertView and shows it. Note, its delegate is self.
- (IBAction)clickButton:(id)sender {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle: #"Test" message: #"Message" delegate: self cancelButtonTitle:#"Cancel" otherButtonTitles:#"Next", nil];
[alert show];
}
Lastly, you create the delegate function. This is triggered whenever a button is clicked in the alertView. buttonIndex corresponds to the button that was clicked, so we want to present the view when the 1st button is pressed (0 corresponds to cancel, 1 corresponds to next). Realistically, you'd probably want to present a custom view controller that you define.
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
if(buttonIndex == 1){
UIViewController* newView = [[UIViewController alloc] init];
[self presentViewController:newView animated:YES completion:nil];
}
}

UIAlertView hidden behind View

I presented a view controller with the presentModalViewController:animated: method. Let's call this view myView. On myView, I have a button and when tapped, it is supposed to create a UIAlertView and show it.
However, when I tap this button, the alertView is created but doesn't appear on top of myView. Instead, it is hidden behind myView.
Note: To verify that the alertView is hidden behind myView, I added a second button on myView and when it is tapped, the myView view controller dismisses itself (i.e. [self dismissModalViewControllerAnimated:YES]). When myView is dismissed, the alertView appears.
Any idea why the alertView is hidden behind myView?
I think after you show UIAlertView you are adding a subview on UIWindow. And it's above UIAlertView in UIWindow layer.
Make sure you don't add anything on UIWindow as it is not a good practice.
If you still wan t to carry out adding subview just send that subviewToBack:
Best of luck
I have the same problem. In Controller1, I present Controller2, and in Controller2 I create an alertView. But the alertView is put behind the Controller2's view.
I found that if I create the alertView not from any viewController(by using a public function), the alertView will appear at the front of the screen.
+ (void)alertShow:(NSString *)alertMsg {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:alertMsg
message:nil delegate:self
cancelButtonTitle:nil
otherButtonTitles:nil];
[alert show];
[self performSelector:#selector(dismissAlertView:) withObject:alert afterDelay:2.5];
}
Hope it will solve your problem.
I was having this problem, also. It was intermittent, but I believe it related to displaying the alert on a background thread.
As of Xcode 9, Swift 4:
To test if function is running on main thread, use: Thread.current.isMainThread
print("On main thread: \(Thread.current.isMainThread)")
To display alert, or perform any operation on main thread, use OperationQueue.main:
OperationQueue.main.addOperation {
// code to perform on main thread
}

Asking for user confirmation before leaving a view in iOS

I need to show an UIAlertView before a user leaves a certain view, either by tapping a 'back' navigation bar button or by tapping one of the tab items in the tab bar I have, in order to ask him for confirmation. It would be a two-button alert, a 'Cancel' one to stay in the view, and an 'Accept' one to leave. I need to do this because I have to make the user aware that unsaved changes will be lost if leaving.
I tried to do this by creating and showing the alert view in the viewWillDisappear: method:
- (void)viewWillDisappear:(BOOL)animated
{
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(#"Exit", #"")
message:NSLocalizedString(#"Are you sure you want to leave? Changes will be discarded", #"")
delegate:self
cancelButtonTitle:NSLocalizedString(#"Cancel", #"")
otherButtonTitles:NSLocalizedString(#"Accept", #""), nil];
[alertView show];
[super viewWillDisappear:animated];
}
But the view is pop anyway, and the alert view is shown after that and app crashes since its delegate is the view controller that has been already pop from the navigation stack... I don't find the way to solve this scenario, can anybody help me?
Thanks!
Showing the alert view when viewWillDissapear won't work, because the view is already dissapearing, it's on its way to be removed.
What you can do, is add yourself a custom action when the back button is pressed, then you decide what to do when the back button is pressed, you can show the alert view, and then in one of the buttons procedd to dismiss the view, something like this:
- (id)init {
if (self = [super init]) {
self.navigationItem.backBarButtonItem.target = self;
self.navigationItem.backBarButtonItem.action = #selector(backButtonPressed:);
}
return self;
}
Then show the alert view when the back button is pressed:
-(void)backButtonPressed:(id)sender
{
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(#"Exit", #"") message:NSLocalizedString(#"Are you sure you want to leave? Changes will be discarded", #"") delegate:self cancelButtonTitle:NSLocalizedString(#"Cancel", #"") otherButtonTitles:NSLocalizedString(#"Accept", #""), nil];
[alertView show];
}
Now, when the confirmation button in the alert view is pressed, just call:
[self.navigationController popViewControllerAnimated:YES];
Or do nothing if the user cancels
I would be tempted to move the data manipulation you're trying to protect into a modal view controller and handle the validation on whatever action you choose to have dismiss the modal presentation. To me, that's the point of modal: something that has to be completed before interacting with the rest of the app.

Disabling ActionSheet dismiss on ipad when user touches outside popover

I have an actionsheet showing in my app and it all work fine on the iphone. However, on ipad it automatically creates the actionsheet within a popover and I can't get it to disable the dimissing whe the user touches outside the actionsheet.
I have changed how the actionsheet is displayed for the ipad and is now shown using:
UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:#"" delegate:self cancelButtonTitle:#"Cancel" destructiveButtonTitle:nil otherButtonTitles:#"Choose a preloaded picture", #"Use a photo", nil];
actionSheet.actionSheetStyle = UIActionSheetStyleBlackOpaque;
actionSheet.tag = 1;
[actionSheet showFromRect:CGRectMake(100, 0, 300, 300) inView:self.view animated:YES];
[actionSheet release];
I have also tried using.
- (BOOL)popoverControllerShouldDismissPopover:(UIPopoverController *)popoverController
{
return NO;
}
Any idea on how I can stop the uiactionsheet popover from dismissing when a user touches outside the actionsheet?
Your popoverControllerShouldDismissPopover: method won't be called because it's a UIPopoverControllerDelegate method, and you're dealing with a UIActionSheet. Since the UIKit automatically creates the popover controller for you, you won't get a chance to set its delegate. You could access the popover view itself with [popoverActionsheet superview], but that won't give you the UIPopoverController.
From a user experience standpoint, Apple would ask you not to implement such behavior— if you need to present options in a modal fashion (where they don't go away until the user makes a choice) then the user will be more familiar with a UIAlertView, or a modally-presented view controller of your own.

iOS UIActionSheet presented from LongPress gesture on a button erroneously requires double clicking buttons to dismiss

I have a tabbar application, in one of the tabs I have a MKMapView. In this view, my viewDidLoad I am initializing a long press gesture recognizer for a UIButton. When this button is pressed and help it presents a UIActionSheet with 5 buttons + the cancel button. Each button represents a zoom level: "World", "Country", "State", "City", "Current Location". Selecting a button in the UIActionSheet zooms the underlying MKMapView to that level.
The problem I am having is that all of the buttons (including the cancel button) require double-tapping to dismiss the UIActionSheet. This is not the intended behavior -- it should dismiss after pressing the button once like every other UIActionSheet. After the first press I can see the map zooming to the appropriate level behind the UIActionSheet so I know the touch is registering on the correct button, but the button does not highlight blue upon the first press and the UIActionSheet does not dismiss. Not until I press the button for a second time does it highlight blue and then dismiss.
If I remove the longpress gesture recognizer and present the UIActionSheet on a 'touch up inside' then everything works as it is supposed to. So I know the gesture is somehow interfering, any ideas on a fix or workaround? Or is this a bug that should be reported to Apple?
- (void) viewDidLoad {
// intitialize longpress gesture
UILongPressGestureRecognizer *longPressRecognizer = [[UILongPressGestureRecognizer alloc]
initWithTarget:self
action:#selector(zoomOptions:)];
longPressRecognizer.minimumPressDuration = 0.5;
longPressRecognizer.numberOfTouchesRequired = 1;
[self.currentLocationButton addGestureRecognizer:longPressRecognizer];
}
- (IBAction) zoomOptions:(UIGestureRecognizer *)sender {
NSString *title = #"Zoom to:";
UIActionSheet *zoomOptionsSheet = [[UIActionSheet alloc] initWithTitle:title delegate:self cancelButtonTitle:#"Cancel" destructiveButtonTitle:nil otherButtonTitles:#"World", #"Country", #"State", #"City", #"Current Location", nil];
[zoomOptionsSheet showFromTabBar:appDelegate.tabbarController.tabBar];
}
Anna Karenina was right, and the link provided helped me figure it out. Basically, UILongPressGestureRecognizer is a "continuous gesture" which undergoes various state changes. I needed to check for the appropriate state, which in my case is UIGestureRecognizerStateBegan since I want the UIActionSheet presented after holding the button down but before you release and stop the gesture. All I had to do was wrap the presentation of the UIActionSheet in an if statement that checked for the appropriate state. Now it works as expected.
- (IBAction) zoomOptions:(UILongPressGestureRecognizer *)sender {
if (sender.state == UIGestureRecognizerStateBegan) {
NSString *title = #"Zoom to:";
UIActionSheet *zoomOptionsSheet = [[UIActionSheet alloc]
initWithTitle:title
delegate:self
cancelButtonTitle:#"Cancel"
destructiveButtonTitle:nil
otherButtonTitles:#"World", #"Country",
#"State", #"City",
#"Current Location", nil];
[zoomOptionsSheet showFromTabBar:appDelegate.tabbarController.tabBar];
}
}

Resources