Validating UITextField input and displaying error - iphone

What's the best practice for validating user input in a UITextField and displaying error? I tried performing checks inside textFieldShouldReturn and textFieldShouldEndEditing and popping up an error box when userever inputted invalid content.
However, during testing, the popup from textFieldShouldEndEditing is getting invoked multiple times. My simple test was to input invalid text in TextFieldA and navigate directly to TextFieldB. I observed 3 error popups, all generated by textFieldShouldReturn

It might be that by showing the alert, the edit field loses focus and thereby issues another textFieldShouldReturn or textFieldShouldEndEditing.
Try postponing the alert, by putting the code to show the alert into a separate method, and invoke that method from textFieldShouldEndEditing with [self performSelector:#selector(yourAlertMethod) withObject:nil afterDelay:0]
Actually, I found that I had a similar problem a while ago. The trick is to make sure that the text field doesn't have the focus any more for the duration that you show the alert.
Here's my solution:
// declare this in your interface as part of your ivars:
UITextField *currTextField;
// here comes the code to check for bad input and show an alert:
#pragma mark -
#pragma mark UITextFieldDelegate
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
if ([self acceptsEntry:textField.text]) { // this checks if the text is valid
// We're done here
return YES;
} else {
// Setting the delegate to nil will prevent the textField to listen for Return key events.
textField.delegate = nil;
// Removing the observer will prevent the user from typing into the textfield using an external keyboard
[textField resignFirstResponder];
currTextField = textField;
UIAlertView *av = [[UIAlertView alloc] initWithTitle:NSLocalizedString(#"invalidEntry", #"")
message:NSLocalizedString(#"invalidEntryMessage", #"")
delegate:self
cancelButtonTitle:NSLocalizedString(#"OK", nil) otherButtonTitles:nil];
[av show];
[av release];
}
return NO;
}
#pragma mark -
#pragma mark UIAlertViewDelegate
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
// gets called when the user dismisses the alert view
currTextField.text = #""; // erase bad entry
currTextField.delegate = self; // We want to listen for return events again
[currTextField becomeFirstResponder];
currTextField = nil;
}

Related

Showing UIActionSheet over UIAlertView

For a specific server notification I am supposed to show an UIActionSheet. But problem here is when that event comes, at the same time if any UIAlertView already showing on any view controller, it make the UIActionSheet disabled( After pressed ok for alert view I am not able to select anything on view controller , view got disabled because of UIActionSheet). Anyone faced this kind of problem, Any idea how to solve it?
I have tried by dismissing alert view before showing action sheet, however which alert view do I need to dismiss as I have many alert view in many controller. All are local to that controllers. How to solve this problem.
Note:
Same problem won't come for iPod, as it wont allow to click ok before responding to UIActionSheet.
Take a Global Alert view named it as activeAlertView. Now when you show a alert view please check that alert view and then show and assign. Like
declare a property in .h and synthesize it
#property (nonatomic, retain) UIAlertView *activeAlertView;
then use the below code when try to show an alert.
if(self.activeAlertView){
[self.activeAlertView dismissWithClickedButtonIndex:0 animated:YES];
}
UIAlertView *localAlert = [[UIAlertView alloc] initWithTitle:#"Title" message:#"Your message" delegate:nil cancelButtonTitle:#"cancel" otherButtonTitles:nil, nil ];
[localAlert show];
self.activeAlertView = localAlert;
[localAlert release];
this way your activeAlertview will keep the current aler view's reference and before show the actionSheet dismiss the alert view.
For Identified which alert-view you must set Tag or alert-view.
Ex:-
alertviewName.tag=1;
Then you can check is there alert-view Open in Particular view-controller sub-views use bellow code like:-
- (BOOL) doesAlertViewExist {
for (UIView* view in yuorviewcontroller.view.subviews) {
BOOL alert = [view isKindOfClass:[UIAlertView class]];
if (alert)
{
return YES;
}
}
return NO;
}
After this method called you get BOOL value YES or NO If Yes then dismiss it using UIAlertview's Delegate:-
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex;
and put your Actionsheet appear code into didDismissWithButtonIndex method.
When the message comes, first check if there is an alert view.
Show the action sheet after the alert view is dismissed. In didDismiss... you can check a BOOL flag if you now have to show the action sheet or not.
In this case, you should use
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
method rather than,
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
so your code wil be:
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 0)
{
UIActionSheet *actionSheet = ...
[actionSheet showFromTabBar:self.tabBarController.tabBar];
}
}
Thanks
try this:
for (UIWindow* w in [UIApplication sharedApplication].windows)
{
for (NSObject* obj in w.subviews)
{
if ([obj isKindOfClass:[UIAlertView class]])
{
[(UIAlertView*)obj dismissWithClickedButtonIndex:[(UIAlertView*)obj
cancelButtonIndex] animated:YES];
}
}
}

Keyboard dismisses after multiple UIAlertViews are opened

I am having a weird issue with my keyboard automatically closing and then reopening when I open multiple UIAlertViews. If I have a keyboard (from a separate UITextField) and I display a UIAlertView, then upon dismissal of that alert I open another (opening the second one in the didDismissWithButtonIndex). When I dismiss the 2nd one it dismisses the keyboard which then comes back up. If i try this with more than 2 alerts in a row, it will still close my keyboard after the 2nd alert is dismissed, but it doesn't show up until the last alert is dismissed. The problem is that the keyboard delegate functions are NOT called so I cannot respond to it being dismissed. I have other UI elements (textfield and images) that get shifted when the keyboard opens, so when it closes those elements float in the screen and look strange. Any idea why that keyboard automatically dismisses? Thanks
BTW, I use an NSMutableArray of NSDictionary objects to queue up alerts that need to be displayed if an alert is already displayed. I am not creating and displaying more than 1 alert at a time.
EDIT: Here's sample code. If you run this you'll see both alerts open (0 then 1) after you dismiss '1' you'll see '0' under it. After you dismiss '0' you'll see what I mean - they keyboard briefly closes and opens but no delegate functions are called. If you set i to a value higher than 2, you'll see that the keyboard still closes after dismissing the 2nd alert, but will stay closed until the last alert is dismissed. I also tried opening just 1 UIAlert, and opening the others one at a time from a queue as each one was dismissed, and still noticed that same behavior. Any ideas?
EDIT: After some more digging I found that if I register for the notifications UIKeyboardDidShowNotification and UIKeyboardDidHideNotification they are in fact fired when the keyboard is automatically dismissed and presented. I still would like to know what in the underlying API is causing it to even happen so it can hopefully be avoided.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(0, 100, 320, 48)];
[textField setDelegate:self];
[textField setBackgroundColor:[UIColor redColor]];
[window addSubview:textField];
[textField release];
[self.window makeKeyAndVisible];
return YES;
}
- (BOOL)textFieldShouldReturn:(UITextField *) textField{
NSLog(#"textFieldShouldReturn called with %#", textField);
[textField resignFirstResponder];
return YES;
}
-(void) textFieldDidBeginEditing:(UITextField *)textField
{
NSLog(#"textFieldDidBeginEditing called with %#", textField);
for (int i=0; i< 2; i++) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle: #"test" message: [NSString stringWithFormat:#"%d", i] delegate:self cancelButtonTitle:NSLocalizedString(#"OK",#"") otherButtonTitles:nil];
[alert show];
[alert release];
}
}
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField
{
NSLog(#"++++ textFieldShouldEndEditing %#", textField);
return YES;
}
-(void) textFieldDidEndEditing:(UITextField *)textField
{
NSLog(#"++++ textFieldDidEndEditing %#", textField);
}
the keyboard is only shown when the corresponding UI element is the first responder.. somehow multiple alert views modify the responder chain for a short time. Seems like a framework issue..
I would suggest this workaround:
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
dispatch_async(dispatch_get_main_queue(), ^{
/* show new alert view here */
});
}
EDIT
actually i now think it has to do with the Window hierarchy of the application. UIAlertViews create their own window (at window level UIWindowLevelAlert), make them the key window to recieve touch input and then make the old window key window again upon dismissal. When you show a new alert view on didDismiss, UIKit seems to lose (temporarily) track of key windows and the responder chain..
The fix above of course still applies.

Detecting button pressed when there are multiple alert views

I have multiple alert views in one view, and I use this code to detect which button was pressed:
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
NSString *title = [alertView buttonTitleAtIndex:buttonIndex];
if ([title isEqualToString:#"OK"]) {
//for one alert view
[passCode becomeFirstResponder];
} else if ([title isEqualToString:#" OK "]) {
//for another alert view, had to change "OK" to " OK "
[passCodeConfirm becomeFirstResponder];
}
}
Now since there are multiple alert views in one view that do different things, I have to trick the user into thinking "OK" and " OK " are the same thing. It works and looks fine, but it feels kind of messy. Surely there is another way to do this, such as making this specific to an alert view, and then making it specific to another. Do you know how I would do this? Thanks!
It would be more technical as well better that set unique tag for separate UIAlertView and identify it and access in its delegate method.
For example,
UIAlertView *alert=[[UIAlertView alloc]initWithTitle:#"Message" message:#"Are You Sure you want to Update?" delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"Ok",nil];
[alert setTag:1];
[alert show];
[alert release];
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if(alertView.tag == 1)
{
// set your logic
}
}
Use tag property to uniquely identify each of the alertview u create.
Like this
myAlertView.tag = 1
Then in the clickedButtonAtIndex delegate method check which alertview's button was clicked using this tag property ,
if(alertView.tag==1)
I wouldn't use the titles to distinguish between the buttons. You'll run into problems when your app is localized or you decide to change the button titles, but forget to update them everywhere. Use the button indexes instead or if you only have one button in addition to a cancel button, use the cancelButtonIndex property of UIAlertView.
To distinguish between multiple alert views, you could use their tag property.
In your view, add a property for each alert view.
UIAlertView *myAlertType1;
UIAlertView *myAlertType2;
#property (nonatomic, retain) UIAlertView *myAlertType1;
#property (nonatomic, retain) UIAlertView *myAlertType2;
Create your alert using these properties
self.myAlertType1 = [[[UIAlertView alloc] initWithTitle: ... etc] autorelease];
[self.myAlertType1 show];
Then in your delegate method:
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (alertView == myAlertType1) {
// check the button types and add behaviour for this type of alert
} else if (alertView == myAlertType2 {
// check the button types and add behaviour for the second type of alert
}
}
Edit: Although the above works, iApple's suggestion of using the tag seems cleaner/simpler.
//in your .h file
UIAlertView* alert1;
UIAlertView* alert2;
//in your .m file
// when you are showing your alerts, use
[alert1 show]; //or
[alert2 show];
//and just check your alertview in the below method
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if(alertView == alert1)
{
//check its buttons
}
else //check other alert's btns
}

Dismiss the keyboard if action sheet is going to popup

I have a UIDatePickerView inside UIActionSheet as a input to the UITextField. When focusing on UITextField UIActionSheet will popup instead of Keyboard. When clicking on the done button in the UIActionSheet it'll hide. I have several other text fields behave as normal (Showing keyboard).
- (void)textFieldDidBeginEditing:(UITextField *)textField {
if ([textField isEqual:txtExpDate]) {
[textField resignFirstResponder];
[self showDatePicker];
}
}
- (void) showDatePicker{
UIActionSheet *datePickerActionSheet = [[UIActionSheet alloc] initWithTitle:nil delegate:self cancelButtonTitle:nil destructiveButtonTitle:#"Done" otherButtonTitles:nil];
datePickerView = [[UIDatePicker alloc] initWithFrame:CGRectMake(0, 80, 0, 0)];
datePickerView.datePickerMode = UIDatePickerModeDate;
[datePickerActionSheet addSubview:datePickerView];
[datePickerActionSheet showInView:self.navigationController.view];
[datePickerActionSheet setBounds:CGRectMake(0, 0, 320, 500)];
[datePickerActionSheet release];
}
Now my problem is, Let say first user taps on normal textfield. Which will popups keyboard. Then without selecting done button he taps on date field. Which will popup action sheet (without dismissing the keyboard). After hiding actionsheet user has to tap on other text filed and click on return key of the keyboard.
I want to hide the keyboard if action sheet is going to popup?
Make the text field resignFirstResponder when you are going to bring up the action sheet. If you have more than one text fields, create an instance variable of type UITextField in .h file.
UITextField *currentTextField;
Then you can keep reference of the current text field in textFieldDidBeginEditing: method.
- (void)textFieldDidBeginEditing:(UITextField *)textField {
currentTextField = textField;
....
And, call [currentTextField resignFirstResponder] in showDatePicker method.
- (void)showDatePicker {
[currentTextField resignFirstResponder];
UIActionSheet *datePickerActionSheet = [[UIActionSheet alloc] initWithTitle:nil delegate:self cancelButtonTitle:nil destructiveButtonTitle:#"Done" otherButtonTitles:nil];
...
EDIT: #Samuel Goodwin is correct. We don't have to track the text fields. We can simply do the following to dismiss the keyboard.
- (void)showDatePicker {
[self.view endEditing:YES];
...
If you want to dismiss the keyboard for any reason, simply do:
[[UIApplication sharedApplication] sendAction:#selector(resignFirstResponder) target:nil forEvent:nil];
Worked for all text fields everywhere anywhere.
The method proposed by EmptyStack did not work for me (using xcode 4.2). I placed the message [currentTextField resignFirstResponder] in the method willPresentActionSheet:(UIActionSheet *)actionSheet, that also didn't work. Then I put the message in actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex and that did the trick... strange!
I know this is a very old post but I just thought I'd add the solution that worked for me (the others didn't). I found that I had to re-assign a text field to be first responder the resign it again. It doesn't matter which text field becomes then looses first responder status, this is just a way to ditch that keyboard.
[self.textfield becomeFirstResponder];
[self.textfield resignFirstResponder];
-(BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
if ([textField isEqual:txtExpDate]) {
[self showDatePicker];
return NO;
}
return YES;
}
It is working fine.
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField{
if(textField==myTextField2){
[myTextField1 resignFirstResponder];
[self showActionSheet];
return NO;
}
return YES;
}

Using UIAlertView in a manner similar to Windows' MessageBox()?

I'm new to the iPhone and I would like to be able to use UIAlertView in a manner similar to the Windows MessageBox() or the MessageDlg() in Delphi.
For example, I have a method that needs to ask the user for confirmation on something, and proceed based on their response.
E.g. (pseudocode):
-(void)doSomething
{
[doStep1];
[doStep2];
var myValue = [getDefaultValue];
if (myValue = nil)
{
if [promptUser(#"No value in setting. Use the default value?")] //UIAlertView here?
{
myValue = #"defaultValue";
}
else
return; // bug out of the routine 'cause we have no value.
}
[doStep3 withValue:myValue];
}
Or, put put it another way- is there a way of using UIAlertView to ask the user a question within a routine as a way of controlling the logic flow of that routine?
There's no reason to subclass UIAlertView at all. That's what delegates are for. All you need is a class (such as your view controller) supporting the UIAlertViewDelegate protocol, and set the UIAlertView's delegate property to that class. You then implement the alertView:clickedButtonAtIndex: method in this class, and the alertViewCancel: method if you want to specifically handle cancellations differently.
You can read more about it in the UIAlertView documentation and in the UIAlertViewDelegate documentation.
I have no idea what MessageDlg() is, but you can certainly subclass UIAlertView and handle the dialog response, based on which button is pressed, e.g.:
Set up the UIAlertView subclass header:
//
// ARReachabilityAlertView.h
//
#import <UIKit/UIKit.h>
#interface ARReachabilityAlertView : UIAlertView <UIAlertViewDelegate> {
}
#end
Set up the UIAlertView subclass implementation:
//
// ARReachabilityAlertView.m
//
#import "ARReachabilityAlertView.h"
#implementation ARReachabilityAlertView
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self setTitle:#"Error"];
[self setMessage:#"This application won't run without a network connection. Do you want to quit?"];
[self addButtonWithTitle:#"Quit"];
[self addButtonWithTitle:#"Continue"];
[self setDelegate:self];
}
return self;
}
- (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex == 0)
exit(0); // quit application if "Quit" is pressed; otherwise, do nothing
}
- (void) drawRect:(CGRect)rect {
[super drawRect:rect];
}
- (void) dealloc {
[super dealloc];
}
#end
Note the alertView:clickedButtonAtIndex: delegate method. This handles the conditionals you use to decide how the application proceeds. You can send an NSNotification from here, or call a method in the application delegate, whatever you want.
In my example, this UIAlertView is instantiated if there is no network connection, and the application is closed if the user clicks "Quit" in the alert view. Otherwise, if the user clicks "Continue" the application keeps running as usual.
Note that the implementation of the subclass requires the drawRect: method be called. I'm not sure if this is a bug or not, since I'd expect the drawRect: method to be called in the super-class; I filed a bug report with Apple on this one, but I haven't heard anything. Comment it out if you want to see what the effect will be — it's kind of interesting.
I believe you're looking to use UIAlertView as a modal alert box (in the sense that you'd like your code to stop running until the user makes a selection). There's no easy way to do this, and it's really recommended that you NOT code for the iPhone in this manner. I think Alex's explanation is a good solution.
To do this, what you can do is to run the mainloop manually. I have not managed to stop the mainloop directly, so I instead run the mainloop for 0.5 seconds and wait until the user responds.
I ran into this question while researching the problem for a C#/MonoTouch user on the iPhone. The sample below is written for MonoTouch/C# but should be trivial to translate to Objective-C
The following function shows how you could implement a modal query with the above approach:
int WaitForClick ()
{
int clicked = -1;
var x = new UIAlertView ("Title", "Message", null, "Cancel", "OK", "Perhaps");
x.Show ();
bool done = false;
x.Clicked += (sender, buttonArgs) => {
Console.WriteLine ("User clicked on {0}", buttonArgs.ButtonIndex);
clicked = buttonArgs.ButtonIndex;
};
while (clicked == -1){
NSRunLoop.Current.RunUntil (NSDate.FromTimeIntervalSinceNow (0.5));
Console.WriteLine ("Waiting for another 0.5 seconds");
}
Console.WriteLine ("The user clicked {0}", clicked);
return clicked;
}
Just a quick tip: if you want a class to be a delegate for more than one UIAlertView, just use tag property to tell who is who:
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Clear all" message:#"Are you sure you want to erase everything?" delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"OK", nil];
alert.tag = ALERT_DELETE_TAG;
[alert autorelease];
[alert show];
Delegate methods are called with UIAlertView as first argument, and you can check who's the originator there.
Thanks to #miguel.de.icaza I found my solution like below (part of the code):
#interface MyClass: NSObject <UIAlertViewDelegate>
{
int confirmed;
}
- (BOOL) removeObjectAtIndex:(NSUInteger)index;
#end
#implementation
- (BOOL) removeObjectAtIndex:(NSUInteger)index
{
// delete confirmation alert
confirmed = -1;
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Delete confirmation" message:#"Are you sure to delete object" delegate:self cancelButtonTitle:#"Yes" otherButtonTitles: #"No", nil];
alert.tag = 2;
[alert show];
[alert release];
// wait for confirm (0 or 1)
while (confirmed == -1) {
// this is what you need!!!
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5]];
}
if (confirmed) {
[myObjects removeObjectAtIndex:index];
return YES;
}
else
return NO;
}
- (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
switch (alertView.tag) {
case 1:
// ...
break;
case 2:
if (buttonIndex == 0) // delete confirmed
confirmed = 1;
else // dismiss
confirmed = 0;
break;
default:
break;
}
}
#end