iOS How to dismiss UIAlertView with one tap anywhere? - ios

I want to dismiss UIAlertView anywhere outside it with one tap. I want to show a UIAlertView without any button.
I have standard UIAlertView codes here, but I need input how to dismiss it, if it is possible.
With UITouch? With UITapGestureRecognizer?
Thank you.
EDIT:
in viewDidLoad
alertview initialization here with name "alert"
if (alert)
{
emptyview = [[UIView alloc]initWithFrame:CGRectMake(0,0,320,480)];
emptyview.backgroundColor = [UIColor clearColor];
[self.view addSubview:emptyview];
[emptyview addSubview:alert];
NSLog (#"emptyview is there!");
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleSingleTap:)];
[emptyview addGestureRecognizer:singleTap];
[singleTap release];
}
But this emptyview doesnt not respond at all and it does not respond to handleSingleTap selector, which I rewrote a bit:
-(void)handleSingleTap:(UITapGestureRecognizer *)sender{
[alert dismissWithClickedButtonIndex:0 animated:YES];
[emptyview removeFromSuperview];
}
I need this emptyview being upon alert when alert is shown then I can dismiss alert with one tap.
I tried:
if (alert)
{
emptyview = [[UIView alloc]initWithFrame:CGRectMake(0,0,320,480)];
emptyview.backgroundColor = [UIColor clearColor];
[self.view addSubview:emptyview];
[emptyview addSubview:alert];
NSLog (#"emptyview is there!");
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleSingleTap:)];
[alert addGestureRecognizer:singleTap];
[singleTap release];
}
Of course, alert did respond to handleSingleTap function. What did I wrong with emptyview?
SECOND EDIT:
What I want to achieve in this case is to show a small view with explanation after selecting a word, similar function in Kindle app, if you have one.
Maybe I should create a UIView instead of UIAlertView? But the small view in Kindle app is so nice with shadow below it, how is it possible?

It sounds like you are essentially trying to recreate a "Toast" on iOS. Good news, someone has already done that. See this project.
Edit: Don't want to use iToast. I like your style, less code it is. Here is what I come up with. It would seem obvious as others have said that the only way to overcome the modal nature of the UIAlertView is to add a superview to handle touch events. But you don't have to do that manually every time, consider subclassing UIAlertView. Try something like this:
Edit: #wagashi, Thanks for accepting my answer, and thanks for the heads up about setFrame: being a good place to adjust the size. Your code does make a very toast-like little alert, however when I tried it I found that if the message was to long the view seemed to fall apart. So I have modified setFrame: to simply reduce the size of the alert by about the size of one button, and to remain centered on the screen. So that the class accurately answers the question title "iOS How to dismiss UIAlertView with one tap anywhere?"
NoButtonAlertView.h
#import <UIKit/UIKit.h>
#interface _NoButtonAlertViewCover : UIView
#property (nonatomic,assign) UIAlertView *delegate;
#end
#interface NoButtonAlertView : UIAlertView
-(id)initWithTitle:(NSString *)title message:(NSString *)message;
#end
NoButtonAlertView.m
#import "NoButtonAlertView.h"
#implementation _NoButtonAlertViewCover
#synthesize delegate = _delegate;
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
[self removeFromSuperview];
[_delegate dismissWithClickedButtonIndex:0 animated:YES];
}
#end
#implementation NoButtonAlertView
-(void)show{
[super show];
_NoButtonAlertViewCover *cover = [[_NoButtonAlertViewCover alloc] initWithFrame:[UIScreen mainScreen].bounds];
cover.userInteractionEnabled = YES;
cover.backgroundColor = [[UIColor lightGrayColor] colorWithAlphaComponent:.01];
cover.delegate = self;
[self.superview addSubview:cover];
}
-(id)initWithTitle:(NSString *)title message:(NSString *)message{
if ((self = [super initWithTitle:title message:message delegate:nil cancelButtonTitle:nil otherButtonTitles:nil, nil])){
}
return self;
}
- (void)setFrame:(CGRect)rect {
// Called multiple times, 4 of those times count, so to reduce height by 40
rect.size.height -= 10;
self.center = self.superview.center;
[super setFrame:rect];
}
#end
With this simple UIAlertView subclass and its UIView subclass for a cover, you can use it as simply as you would a standard UIAlertView. Like so:
NoButtonAlertView *alert = [[NoButtonAlertView alloc] initWithTitle:#"Hello" message:#"I am the very model of a modern major general; I'm information, vegitable, animal, and mineral."];
[alert show];
Will yield:

After showing UIAlertView add to your view controller (or even window) new empty UIView with full screen size. Attach to this wiew UITapGestureRecognizer
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleSingleTap:)];
[view addGestureRecognizer:singleTap];
[singleTap release];
Now in handleSingleTap method you can dismiss UIAlertView and remove this view from window
-(void)handleSingleTap:(UITapGestureRecognizer *)sender{
[myAlert dismissWithClickedButtonIndex:0 animated:YES];
[view removeFromSuperView];
}

Related

Best way to take a UIGestureRecognizer event and get the root container view?

I have a UIViewController where I'm adding bunch of UIViews as subviews. Then I attach UIGestureRecognizer to each, so I can be notified when there's a tap. Here's the code inside the UIViewController:
- (void)attachSubview{
UIImageView *childView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"child"]] ;
UITapGestureRecognizer *singleFingerTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapped:)];
[childView addGestureRecognizer:singleFingerTap];
[self.view addSubView childView];
}
- (void)tapped:(UITapGestureRecognizer *)recognizer{
NSLog(#"Tapped view : %#", recognizer.view);
NSLog(#"The root view is : %#", self.view);
}
Now, here's the problem: I actually want to extract these two methods out into a separate class. In this case I can't use the self.view inside the tapped: method, since self wouldn't be the viewcontroller class anymore.
So I would like a simple and efficient way to just take the recognizer object and somehow get the root view to which the image view belongs. What is the best and future proof way to do this?
Could you try this please?
- (void)viewDidLoad {
[super viewDidLoad];
//example of its use. You can replace with [className attachSubview:self.view] for example
self.view = [self attachSubview:self.view];
}
- (UIView *)attachSubview:(UIView *)passedInView{
UIImageView *childView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"child"]] ;
UITapGestureRecognizer *singleFingerTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapped:)];
[childView addGestureRecognizer:singleFingerTap];
[passedInView addSubview:passedInView];
return passedInView;
}
- (void)tapped:(UITapGestureRecognizer *)recognizer{
NSLog(#"Tapped view : %#", recognizer.view);
NSLog(#"The root view is : %#", recognizer.view.superview);
}

UIView doesn't respond to touches

I have simple view controller and I add this controller like subview for window using this code:
UIWindow *window = [UIApplication sharedApplication].keyWindow;
if (!window)
window = [[UIApplication sharedApplication].windows objectAtIndex:0];
self.view.alpha = 0.0;
self.view.userInteractionEnabled = YES;
[window addSubview:self.view];
[self.view addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(closeTriggered:)]];
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionAllowUserInteraction animations:^{
self.view.alpha = 1.0;
}completion:nil];
But when I click at this view - nothing happens. Even event touchesBegan: are not called.
UPD:
Code above is in -(void)show method. I want to show one controller above all controllers.
In FirstViewController I create instance of CustonAlertViewController like that:
CustomAlertViewController *alertVC = [[CustomAlertViewController alloc]init];
[alertVC show];
In CustomAlertViewController I have show method presented at the top and viewDidLoad method with:
self.view.backgrondColor = [UIColor greenColor];
Hidden views (and equivalently, those with alpha == 0.0) do not respond to touches. If you require a fully transparent view, leave alpha as > 0.0, and say...
self.view.backgroundColor = [UIColor clearColor];
Alternatively, assign a nonzero alpha.
EDIT yes, the CustomAlertViewController instance is being deallocated immediately after show is invoked. The view controller that does the allocating needs to have a strong property to keep the alert around,
#property(nonatomic,strong) CustomAlertViewController *alertVC;
and adding...
CustomAlertViewController *alertVC = [[CustomAlertViewController alloc]init];
self.alertVC = alertVC;
[alertVC show];
This doesn't try to address some potential problems beyond the scope of this question (like rotation, or cleanly restoring when the alert is done).
// try like this , u need to give number of touches
UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(closeTriggered:)];
gesture.numberOfTapsRequired = 1;
gesture.numberOfTouchesRequired = 1;
[self.view addGestureRecognizer:gesture];
You have been setting your UITapGestureRecognizer after you addSubview to your window. like:
You have been doing this
//...
[window addSubview:self.view];
[self.view addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(closeTriggered:)]];
//...
Try this
Set UITapGestureRecognizer before you addSubview. And remember to add the UIGestureRecognizerDelegate to your ViewController
self.view.alpha = 0.2;
self.view.userInteractionEnabled = YES;
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(closeTriggered:)];
tapGesture.numberOfTapsRequired = 1;
[tapGesture setDelegate:self];
[self.view addGestureRecognizer:tapGesture];
// After you add your self.view to window
[window addSubview:self.view];
I hope this can help you.

Tapping the Background image on iPhone

I want to do something when the background was tapped on iPhone.
How to enable the background tapping?
I put a sample background on my app using this code.
- void viewDidLoad{
[super viewDidLoad];
UIGraphicsBeginImageContext(self.view.frame.size);
[[UIImage imageNamed:#"backgroundimage.jpeg"] drawInRect:self.view.bounds];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
How to do background tapping?
For example I want to click the background and a message will pop up "Background was tapped!".
Do i need IBAction for that?
Need help.
What you could do is use an IBAction, which would be a lot simpler code.
In your header file of your view controller, do this:
- (IBAction)backgroundTap:(id)sender;
In your implementation file of your view controller, do this:
- (IBAction)backgroundTap:(id)sender
{
}
In the storyboard, assuming that you have linked up view controller GUI with your view controller class, click on the background of the view controller GUI and then show the Identity Inspector in the Utilities view, which should show up on the right. Then, under custom class, which should currently be blank, type in UIControl. Now go to the connections inspector and link at Touch Down to the background, selecting backgroundTap. Now, anything inside the backgroundTap method will be what will happen when you select the background.
UIButton is the simplest way.
- (void)backgroundButtonClicked:(id)sender
{
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:nil message:#"Background was tapped!" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alertView show];
[alertView release];
}
- (void)viewDidLoad
{
[super viewDidLoad];
/*
* Your other code here
*/
UIButton *backgroundButton = [UIButton buttonWithType:UIButtonTypeCustom];
backgroundButton.backgroundColor = [UIColor clearColor];
backgroundButton.frame = self.view.bounds;
[backgroundButton addTarget:self action:#selector(backgroundButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:backgroundButton];
[self.view sendSubviewToBack:backgroundButton];
}
BTW, there is no need to draw a background image because [UIImage imageNamed:#"imagename"] returns an image. If you want to present it, try putting the code in your -viewDidLoad:
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"backgroundimage.jpeg"]];
imageView.frame = self.view.bounds;
[self.view insertSubview:imageView belowSubview:backgroundButton];
[imageView release];
Edit:
Thanks to #AlexMDC for reminding me of UITapGestureRecognizer. Here is the UITapGestureRecognizer version:
- (void)tapped:(UITapGestureRecognizer *)g
{
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:nil message:#"Background was tapped!" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alertView show];
[alertView release];
}
- (void)viewDidLoad
{
[super viewDidLoad];
/*
* Your other code here
*/
UITapGestureRecognizer*tap = [[UITapGestureRecognizer alloc] init];
[tap addTarget:self action:#selector(tapped:)];
[self.view addGestureRecognizer:tap];
[tap release];
}
Both versions meet the requirements. Admittedly, UITapGestureRecognizer is more powerful and more flexible. However, I'd prefer UIButton to do the trick this time. It's more lightweight than gesture recognizer. I needn't care about the state of the gesture recognizer, whether touch events have been blocked by it or how to implement the UIGestureRecognizerDelegate.
It's more likely the case that we want to add some other UIView or subclasses of UIView on the controller's view. At this point, the UITapGestureRecognizer version need to exclude all the non-background areas in – gestureRecognizerShouldBegin: delegate method.
If detecting double tap is the new requirement, it is still not too late to refactor the UIButton to the UITapGestureRecognizer.

Activity Indicator View not spinning

I have below code where I am loading some link in webview.
- (void)viewDidLoad
{
mySpinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[self openWebPage:fileName];
[self.view addSubview:myWebView];
[self.view addSubview:mySpinner];
mySpinner.center = CGPointMake(self.view.frame.size.width / 2.0, 100);
}
-(void)openWebPage:(NSString*)address {
NSURL*url=[NSURL URLWithString:address];
NSURLRequest*request=[NSURLRequest requestWithURL:url];
myWebView.scalesPageToFit = NO;
[myWebView loadRequest:request];
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
UIAlertView *errorView = [[UIAlertView alloc] initWithTitle:#"Error!!!" message:#"Please make sure you are connected to 3G or Wi-Fi." delegate:nil cancelButtonTitle:#"Dismiss" otherButtonTitles:nil, nil];
[errorView show];
mySpinner.hidden = YES;
[mySpinner stopAnimating];
}
-(void)webViewDidStartLoad:(UIWebView *)webView2 {
NSLog(#"webViewDidStartLoad");
mySpinner.hidden = NO;
[mySpinner startAnimating];
NSLog(#"step 1");
NSDate *future = [NSDate dateWithTimeIntervalSinceNow: 2 ];
[NSThread sleepUntilDate:future];
NSLog(#"step 2");
}
-(void)webViewDidFinishLoad:(UIWebView *)webView2 {
NSLog(#"webViewDidFinishLoad");
mySpinner.hidden = YES;
[mySpinner stopAnimating];
}
What I am doing is at webViewDidStartLoad I am displaying spinner and starting animating using [mySpinner startAnimating];, but it didn't spin. It just stays as it is (no spinning).
Any idea what is going wrong?
Edit 1
I have webview delegate #interface WebDetailsViewController : UIViewController<UIWebViewDelegate>
Also I have added [NSThread sleepUntilDate:future]; just to verify whether activity indicator view is animating or not.
Below is what I have from NSLog
2013-06-23 16:29:28.843 GulfLab[2048:907] webViewDidStartLoad
2013-06-23 16:29:28.845 GulfLab[2048:907] step 1
2013-06-23 16:29:30.847 GulfLab[2048:907] step 2
2013-06-23 16:29:31.836 GulfLab[2048:907] webViewDidFinishLoad
Edit 2
Well well well the problem is in below line...
[UIView beginAnimations: #"Showinfo"context: nil];
[UIView setAnimationCurve: UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.75];
[self.navigationController pushViewController: secondView animated:NO];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.navigationController.view cache:NO];
[UIView commitAnimations];
Below is what I have...
One first view controller, I have buttons and when I click those button I am coming to second view controller and there I am displaying the web file based on button pressed.
My client wanted some effect while coming to second view and for that I added above code. But because of that, I am facing the activity problem.
Any idea what changes do I need to do?
On further investigation I found that problem is in this line...
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.navigationController.view cache:NO];
but I need this line as else animation is not happening...
Also today I noticed that if I touch webview, then it starts animating
Grrr... someone help me
If somebody runs into a similar problem where there Indicator is not spinning:
Make sure the Animating property is set in your storyboard.
It's possible you aren't getting it to animate because you are not on the main UI thread. Try adding it to a dispatch queue on the main loop to force it on the main loop and it should animate.
dispatch_async(dispatch_get_main_queue(), ^{
[mySpinner startAnimating];
}];
Try to put this code after alloc init
mySpinner.hidesWhenStopped = false;
Delete mySpinner.hidden = YES/NO in your code;
Just add [mySpinner setHidesWhenStopped:YES]; in viewDidload
Then you just start and stop.
- (void)viewDidLoad
{
mySpinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[mySpinner setHidesWhenStopped:YES];//set Hidden here
[self openWebPage:fileName];
[self.view addSubview:myWebView];
[self.view addSubview:mySpinner];
[self.view bringSubviewToFront:mySpinner]; //Add if still not show
}
I really think the [NSThread sleepUntilDate:future]; call is killing you in this situation. Most of my code is integration with a 3rd party piece of hardware that will block the main thread in certain scenarios and when that happens I'll have similar behaviors as yours where an element of the UI appears but is in a slightly frozen state until I either tap the screen to "jump start" the UI or the blocking call is completed.
If I were you I would start by getting rid of the sleepUntilDate call then remove the call to stop mySpinner to make sure it is in fact spinning so that when you run into a longer call of webViewDidFinishLoad you'll be assured it's working.
This is how I do a pop-up with a spinner in my code. Not exactly what you had asked but it is somewhat similar.
In your *.h file:
#interface v3ViewController : UIViewController
{
UIAlertView *megaAlert;
}
#property (nonatomic, retain) UIAlertView *megaAlert;
- (IBAction) invokeMegaAnnoyingPopup;
- (IBAction) dismissMegaAnnoyingPopup;
In your *.m file:
#synthesize megaAlert;
- (IBAction) dismissMegaAnnoyingPopup
{
[self.megaAlert dismissWithClickedButtonIndex:0 animated:YES];
self.megaAlert = nil;
}
- (IBAction) invokeMegaAnnoyingPopup
{
self.megaAlert = [[UIAlertView alloc] initWithTitle:#"Please wait..."
message:nil delegate:self cancelButtonTitle:nil
otherButtonTitles: nil];
[self.megaAlert show];
UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc]
initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
indicator.center = CGPointMake(130, 70);
[indicator startAnimating];
[self.megaAlert addSubview:indicator];
}

Can't hide keyboard in UIViewController stack when UIAlertView is on screen

I just spent most of a day tracking down a very strange case where calling resignFirstResponder on the active UITextField did not hide the keyboard, even though the textfield was the first responder. This happens when I push a view controller on top of another view controller with an active text field. The keyboard goes away (as expected). But if I bring the keyboard back by touching a textfield in the 2nd view controller, subsequent calls to resignFirstResponder have no effect.
Here's simple code to reproduce the issue. This code is a view controller with a nav bar button to hide the keyboard, and another to push another copy of itself (with a confirmation UIAlertView). The first copy works without problem. However, if you push a 2nd copy (when the first copy has a visible keyboard) it is impossible to dismiss the keyboard. This only happens if there is a UIAlertView (the confirmation) on the screen when the 2nd copy is pushed. If you remove the #define ALERT line, everything works.
Does anyone know what is happening here? It looks like the UIALertView window is somehow interfering with the keyboard and keeping it's window from disappearing, which then confuses the next view. Is there any solution here other than pushing the 2nd view controller on a timer after the UIALertView is gone?
Sorry for the complex description. This is runnable code. I hope that the code is clear.
#implementation DemoViewController
- (id) init {
if (!(self = [super init]))
return nil;
return self;
}
- (void) dealloc {
[_inputTextfield release];
[super dealloc];
}
- (void) loadView {
UIView *view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
_inputTextfield = [[UITextField alloc] initWithFrame:CGRectMake(0., 0., 320., 44.)];
_inputTextfield.borderStyle = UITextBorderStyleRoundedRect;
_inputTextfield.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
_inputTextfield.keyboardAppearance = UIKeyboardAppearanceAlert;
_inputTextfield.autocapitalizationType = UITextAutocapitalizationTypeNone;
_inputTextfield.autocorrectionType = UITextAutocorrectionTypeNo;
_inputTextfield.keyboardType = UIKeyboardTypeDefault;
[view addSubview:_inputTextfield];
self.view = view;
[view release];
}
- (void) viewWillAppear:(BOOL) animated {
[super viewWillAppear:animated];
UIButton *downButton = [UIButton buttonWithType:UIButtonTypeCustom];
[downButton setTitle: #"keyboard down" forState:UIControlStateNormal];
[downButton addTarget:self action:#selector(downButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
[downButton sizeToFit];
self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithCustomView:downButton] autorelease];
UIButton *nextButton = [UIButton buttonWithType:UIButtonTypeCustom];
[nextButton setTitle: #"next" forState:UIControlStateNormal];
[nextButton addTarget:self action:#selector(nextButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
[nextButton sizeToFit];
self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithCustomView:nextButton] autorelease];;
}
- (void) viewWillDisappear:(BOOL) animated {
[super viewWillDisappear:animated];
[_inputTextfield resignFirstResponder];
}
- (void) downButtonPressed:(id)sender {
[_inputTextfield resignFirstResponder];
}
#define ALERT
- (void) alertView:(UIAlertView *) alertView didDismissWithButtonIndex:(NSInteger) buttonIndex {
if (alertView.cancelButtonIndex == buttonIndex) {
return;
}
[self _nextButtonPressed];
}
- (void) _nextButtonPressed {
DemoViewController *nextViewController = [[DemoViewController alloc] init];
[self.navigationController pushViewController:nextViewController];
[nextViewController release];
}
- (void) nextButtonPressed:(id)sender {
#ifdef ALERT
UIAlertView *alert = [[UIAlertView alloc] init];
alert.message = #"Next view?";
alert.cancelButtonIndex = [alert addButtonWithTitle:#"No"];
[alert addButtonWithTitle:#"Yes"];
alert.delegate = self;
[alert show];
[alert release];
#else
[self _nextButtonPressed];
#endif
}
If you had bad luck resigning your first responders, here are a few solutions that might help:
Determine who has remained the first responder after your last call to resign first responder.
Try resigning all first responders by a single call to self.view (container view)
[self.view endEditing:YES];
ONLY if you've tried all the above methods and none worked, consider using this workaround.
-(BOOL)textViewShouldEndEditing:(UITextView *)textView {
NSArray *wins = [[UIApplication sharedApplication] windows];
if ([wins count] > 1) {
UIWindow *keyboardWindow = [wins objectAtIndex:1];
keyboardWindow.hidden = YES;
}
return YES;
}

Resources