Thursday, February 26, 2009

Alert View with Prompt

Here's another generic class for you. This one doesn't require a navigation app - it can be used pretty much anywhere. It's a custom-subclass of UIAlertView that lets the user type in a value. It supports only two buttons - Okay and Cancel - but it handles everything to do with the text field for you. It looks like this:


You use it pretty much the same way you use an alert view. You allocate and initialize it, call show and then release it:

    AlertPrompt *prompt = [AlertPrompt alloc];
prompt = [prompt initWithTitle:@"Test Prompt" message:@"Please enter some text in" delegate:self cancelButtonTitle:@"Cancel" okButtonTitle:@"Okay"];
[prompt show];
[prompt release];

Then, you implement the appropriate UIAlertView callback method, and grab the entered text from the alert view instance. You have to cast the alert view back to an AlertPrompt instance, but other than that, everything is the same as using a standard UIAlertview:

- (void)alertView:(UIAlertView *)alertView willDismissWithButtonIndex:(NSInteger)buttonIndex
{
if (buttonIndex != [alertView cancelButtonIndex])
{
NSString *entered = [(AlertPrompt *)alertView enteredText];
label.text = [NSString stringWithFormat:@"You typed: %@", entered];
}

}


You can download a sample project that shows how it works right here.

Note: to those of you wondering if this violates the HIG, or will cause problems during review - I don't think it should. Apple does this themselves when they prompt you for a WiFi network password, and I have not used any private APIs or functions whatsoever. I just subclassed an existing public class and extended its functionality in a way that's commonly done.

The code for the class follows:

AlertPrompt.h
//
// AlertPrompt.h
// Prompt
//
// Created by Jeff LaMarche on 2/26/09.

#import <Foundation/Foundation.h>

@interface AlertPrompt : UIAlertView
{
UITextField *textField;
}

@property (nonatomic, retain) UITextField *textField;
@property (readonly) NSString *enteredText;
- (id)initWithTitle:(NSString *)title message:(NSString *)message delegate:(id)delegate cancelButtonTitle:(NSString *)cancelButtonTitle okButtonTitle:(NSString *)okButtonTitle;
@end



AlertPrompt.m
//
// AlertPrompt.m
// Prompt
//
// Created by Jeff LaMarche on 2/26/09.

#import "AlertPrompt.h"

@implementation AlertPrompt
@synthesize textField;
@synthesize enteredText;
- (id)initWithTitle:(NSString *)title message:(NSString *)message delegate:(id)delegate cancelButtonTitle:(NSString *)cancelButtonTitle okButtonTitle:(NSString *)okayButtonTitle
{

if (self = [super initWithTitle:title message:message delegate:delegate cancelButtonTitle:cancelButtonTitle otherButtonTitles:okayButtonTitle, nil])
{
UITextField *theTextField = [[UITextField alloc] initWithFrame:CGRectMake(12.0, 45.0, 260.0, 25.0)];
[theTextField setBackgroundColor:[UIColor whiteColor]];
[self addSubview:theTextField];
self.textField = theTextField;
[theTextField release];
CGAffineTransform translate = CGAffineTransformMakeTranslation(0.0, 130.0);
[self setTransform:translate];
}

return self;
}

- (void)show
{
[textField becomeFirstResponder];
[super show];
}

- (NSString *)enteredText
{
return textField.text;
}

- (void)dealloc
{
[textField release];
[super dealloc];
}

@end





65 comments:

Eric Busch said...

I actually wrote something like this and you may have seen it in my app when you're adding favorite plates. I like the idea of having it as a generic class. :)

Jon Baer said...

Just curious there an svn repository w/ all you samples by chance?

Lyndsay said...
This comment has been removed by the author.
Lyndsay said...

I've implemented the AlertPrompt portion to allow users to name/create a folder. When the prompt is dismissed the table view doesn't get refreshed. I'm guessing this is because I'm not pushing/popping a view controller. Is there a way to force [self.tableView reloadData]; so when the prompt is dismissed the view is reloaded? Calls to reloadData are already in viewWillAppear and viewDidLoad.

Malcolm Hall said...

UIAlertView has an easy but undocumented way for adding text fields:

[alert addTextFieldWithValue:@"" label:nil];

And the delegate is

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
if(buttonIndex==0) // cancel button
return;
NSString* text = [alertView textField].text;
}

Jeff LaMarche said...

Malcolm - that's good to know. Makes sense that it would be there, since Apple has done this in several places. Given the way Apple has been with their review process, I think I would personally continue using this approach until they make that method public. Using private methods is technically a violation of the SDK agreement.

Jon:

I don't have a repository yet, but I will look at setting one up when I can find some free time.

Lyndsay:

When the prompt is dimissed, one of your delegate methods will get called. Can't you just call [self.tableView reloadData]; from there?

Lyndsay said...

I figured out the problem - I wasn't adding the object to the array that was the data source for the table. One I did that and sorted the array, reloadData was working fine!

Alvaro said...

Out of curiosity, how do you deal with having more than one alertview? More specifically, how do you differentiate one alertview from the other when retrieving text in the callback? do you tag your views?

Wilbert said...

Hi Jeff,
Thanks for this tutorial. I actually have you Beginning iPhone Development book and it really helped me a lot in designing my iPhone app.

I have a question, I'm using this alert view with prompt as a password screen, how do you put a validation to prevent the screen from being dismissed if a user presses the OK button without typing anything in the text field?

Thanks

Jamie Chapman said...

Hi Jeff.

Your code produces: wait_fences: failed to receive reply

In OS 3.0, any ideas?

Edison's Labs said...

Been using this for a while. But after upgrading to the 3.0 Seed, the text field resizing does not seem to work correctly. Trying to find a fix, will post here if I do.

Edison's Labs said...

I found that for the -(void)layoutSubviews method, only the if(!layoutDone) clause is called for the 3.0 OS.

But for the 2.2 OS, the if(!layoutDone) clause gets called once, then subsequently the else clause gets called twice, resulting in a final width of 260 for the textfield.

The alertwidth changes for each call to the layoutSubviews method in the 2.2 OS. It first starts off with 312, then 255, then 284. The correct width that should be calculated is 260 (from the 284). I believe layoutSubviews is called everytime the alertview frame size changes like when it's being animated, hence the 3 calls. If that is the case, then 3.0 OS has a bug where UIView doesn't call layoutSubviews at the end of the animation?

Anyhow, my temporary fix is to just use the value 260 for the 3.0 OS (using #if __IPHONE_3_0), or until I can find a better way to fix this. (or if it is a bug, until the bug is fixed)

Michael said...

@Jamie Chapman,

I found a solution for the problem you mention: "wait_fences: failed to receive a response".

You need to resign the textfield as the first responder prior to dismissing the dialog. I fixed it by placing [textField resignFirstResponder] in my clickedButtonAtIndex delegate method.

PtiTaw said...

I haven't understand where you have to put the resign firstResponder to get rid of this minor error

oliv said...

Hi Jeff,

I tried your code and I get the error : wait_fences: failed to receive reply:

So I replaced the show method by the following :

- (void)show {
[super show];
[textField becomeFirstResponder];
}

It means I put the becomeFirstResponder before the show. Then the error disappears. But I can not get a nice behaviour : when I click on "Ok" or "Cancel" button, the application closes.

Any idea about the problem ? Is it linked to the "wait_fences: failed to receive reply:" problem ?

Regards.

oliv said...

Ok, I used the following callback for AlertView and it works fine :

- (void)alertView:(UIAlertView *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex != [actionSheet cancelButtonIndex])
{
[((AlertPrompt*)actionSheet).textField removeFromSuperview];
NSString *entered = [(AlertPrompt *)actionSheet enteredText];
NSLog(@"You typed: %@", entered);
}
}

Ker3m said...

How can I rotate popup message? I have tried CGAffineTransformMakeRotation, but I couldn't make it work.
When I open the application in landscape mode, alert message is still in vertical mode, but keyboard is in landscape mode.

Ker3m said...

I have found the solution here in torynfarr's comment.
To rotate the alert message according to your application orientation put either:

application.statusBarOrientation = UIInterfaceOrientationLandscapeRight;

or

application.statusBarOrientation = UIInterfaceOrientationLandscapeLeft;

...in your AppDelegate's "applicationDidFinishLaunching" method.

This also takes care of keyboard orientation issues as well.

oltbaba said...

I've experienced that your AlertPrompt layout goes to hell if you omit the UIAlertView message.
Calling:
AlertPrompt *prompt = [AlertPrompt alloc];
prompt = [prompt initWithTitle:@"Coupon Code eingeben"
message:@""
delegate:self
cancelButtonTitle:@"Abbrechen"
okButtonTitle:@"OK"];
[prompt show];
[prompt release];

Will cause the alert view buttons to overlap with the textfield.

Jason said...

FYI: I was just rejected by the app store for an update to my program for using the hidden addTextFieldWithValue:label method, even though that's been in my app for months. I'll try this out instead.

hervie said...

notice the message parameter "message:@"Please enter some text in" is not showing in your example. Nor is it showing with my implementation of your sample code on 3.1.2 OS. Any solutions to this? Thanks

David W Sica said...

I'm not sure if this is the 'right' solution, but to get the text/message showing I put in '\n' before my actual text message.

Slava said...

I made the same with username and password: http://iphone-dev-tips.alterplay.com/2009/12/username-and-password-uitextfields-in.html

It may help somebody. Sorry for crossposting.

samv said...

Well done. Just what I needed.

I didn't want to chance the private API.

For multiple alerts I use the tag value of prompt to differentiate between alerts.

sam

aardwolf said...

I'm getting this error when I try to use your class:

/Developer/app/Classes/AlertPrompt.m:11:0 /Developer/app/Classes/AlertPrompt.m:11: error: synthesized property 'enteredText' must either be named the same as a compatible ivar or must explicitly name an ivar

Can you help?

steffenj said...

Thanks for the code. I tried to use it in Landscape mode (in a cocos2d based app) and after adjusting the text field height it works fine.

However, when the alertprompt shows up and i don't enter any text, then shake the iPhone (simulator: shake gesture) the iPhone's undo behavior kicks in and tells me that "There's nothing to undo". That alertbox's button is hidden by the keyboard so i can't dismiss it! Any thoughts on how to fix this?

cory said...

@steffenj

You can disable shake-to-undo with this, apparently.
[UIApplication sharedApplication].applicationSupportsShakeToEdit = NO;

Paul said...
This comment has been removed by the author.
Saul said...

I also had the wait_fences issue (in OS 3.0) and fixed it by :

- (void)alertView:(AlertPrompt *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex != [alertView cancelButtonIndex])
{
NSString *entered = [(AlertPrompt *)alertView enteredText];
NSLog(@" typed: %@",entered);
}
[alertView.textField resignFirstResponder];

}

Note: I had to cast the Delegate method as the custom Alert view type so it would know about the textField property. I see there are other ways of doing this but this seemed to work.

tata Milenki said...

I think that

@synthesize enteredText;

is not necessary, especially in my case (SDK 3.1.2) compiler is complaining with message mentioned by aardwolf.

Simon said...

First off, let me say thanks for providing this class, it is almost exactly the thing I was looking for. The only issue was the cryptic "wait_fences" message that me and other people were getting when using it.

For me, the wait_fences problem seemed to be where you were asking the text field to become first responder. In the -show method the prompt has not yet been added as a subview of a view in the responder chain and this seemed to be causing the error.

I solved the problem by implementing this delegate method as follows:
- (void)didPresentAlertView:(UIAlertView *)alertView
{
[[(AlertPrompt *)alertView textField] becomeFirstResponder];
}

and I completely removed the -show method from the AlertPrompt implementation, as now all it did was call [super show]

Hope this helps someone out!

Thomas said...

Thanks Jeff,

This is exactly what I needed.
Great job with the book too, really helped me make the transition from web coding.

Simon said...
This comment has been removed by the author.
Simon said...

Beauty!
Thanks very much, everyone.

Sallu said...

Nice Work, could you elaborate on textField validation. Okay button should not enabled until textField text is not entered.
Regards,
Praveen

Simon said...

I apologize if this is a novice question, but how do I handle a touch event of the 'RETURN' key on the keyboard using this class?
Many thanks,
-Simon

cyBohemia said...

I've tried all the solutions for getting rid of the wait_fences error but i'm still getting it. weird...

harish said...

Thats really a nice article.
I tried it and its working fine.
I added a couple of more text fields, but in that case the buttons hide behind those textfields. Also, The size of the alert frame remains same and the newly added text fields appear outside the frame. Would it be possible to change the frame size?

Simon said...

Harish - were you (or anyone else) able to resolve the issue of the keyboard on the alert prompt not resigning when the user taps 'Return'? It's causing me great frustration!

davidj said...

To make the return key work, just add this line to initWithTitle:

theTextField.delegate = self;

and then add this method to AlertPrompt.m:

- (BOOL)textFieldShouldReturn:(UITextField *)theTextField
{
[theTextField resignFirstResponder];
[self dismissWithClickedButtonIndex:1 animated:YES];
return YES;
}

kimberly said...

sometimes we decided generic product and usually result a quality product.i think i must to see if Prompt have a well- function just with two buttons. similar to this my boyfriend get generic viagra and the result was absolutely amazing!!

hunt said...
This comment has been removed by the author.
hunt said...
This comment has been removed by the author.
hunt said...

On the comment from David about theTextField.delegate = self;

Please be sure to add "UITextFieldDelegate" to the ViewController or @interface in AlertPrompt.h.

Hope this helps someone.

Edwin said...

scrub m65 kamagra attorney lawyer body scrub field jacket lovegra marijuana attorney injury lawyer

nacho4d said...

About the wait_fences: failed to receive reply: 10004003 message, In my case it appears when showing the alert. I found that it appears when making textField first responder before it is actually shown.
You need to change the - (void) show method to something like:
- (void)show
{
[super show];
[self performSelector:@selector(becomeFirstResponder) withObject:nil afterDelay:0.4];
}
It worked for me ;)

nacho4d said...

I made a mistake in the last post, it should be: [textField performSelector:@selector(becomeFirstResponder) withObject:nil afterDelay:0.4];

RichS said...

You may, or may not be aware, but...

It appears that there are problems with the "CGAffineTransformMakeTranslation" when using this on iOS4.

With this line in, the Alert view was too high, and chopped off at the top of the screen. Removing this line means the view is shown in the correct position.

Justin said...

I ran into the "wait_fences" issue also. I was popping another alert immediately if front-end validation failed on the TextField - I was using it for email capture. To solve my version of the problem, I delayed opening other UIAlertViews, which solved the problem. Like so:

[self performSelector:@selector(showEmailSuccessAlert) withObject:nil afterDelay:0.4];

myuserid said...

What did I do wrong?


Undefined symbols:
"_OBJC_CLASS_$_AlertPrompt", referenced from:
objc-class-ref-to-AlertPrompt in MyViewController.o
ld: symbol(s) not found
collect2: ld returned 1 exit status

Betty said...

1. Create instant variable in the .h file
2. @property
3. @synthesize
4. release in dealloc

Does anyone know why we do #2, 3, 4 for "enteredText"... but never do #1 at all?

I thought you only needed to do 2,3,4... if you had an instant variable. No?

Betty said...

> I was just rejected by the app
> store for an update to my
> program for using the hidden
> addTextFieldWithValue:label
> method,

Since Apple never sees anyone's source-code, how does it know if you've used the illegal addTextField method... or the legal subclassing method?

Betty said...

> To make the return key work,
> just add this [...] and then add
> this method to AlertPrompt.m

I managed to get my RETURN key to work.... but what happens if I'm using AlertPrompt in 2 different places in my controller code?

1. Have RETURN do nothing.
2. Have RETURN dismiss the keyboard.

How would I use #1 in 1 place... and #2 in another place?

dvs said...

Good work. But these lines..

theTextField.backgroundColor = [UIColor clearColor];
theTextField.borderStyle = UITextBorderStyleRoundedRect;

.. make this look a lot nicer.

mklement said...

If you want to use a 4+ base SDK, but also target 3.x iOS versions, you must *conditionally* execute the CGAffineTransfromMakeTranslation line, as demonstrated here:

NSString *iOsVersion = [[UIDevice currentDevice] systemVersion];
if ([[iOsVersion substringToIndex:1] intValue] < 4) { // pre-iOS 4.0
[self setTransform:CGAffineTransformMakeTranslation(0,130)];
}

Note: This is based on the assumption that the placement behavior changed with iOS 4.0.

P.S.:
@Betty:
The "enteredText" property is NOT backed by an instan*ce* variable (it's not "instant"); instead it uses a getter method, - (NSString *)enteredText - to return the text field's contents on demand. Note that the property is declared read-only, so there is no corresponding setter method, and there's also no need to release anything. Therefore, since there is an explicit getter method, the "@synthesize enteredText" line is pointless, and can be removed. (In general, I think, explicit getter/setters take precedence over those generated by a @synthesize statement.)

Re ability to vary behavior of the Return key: add a property to the AlertPrompt class that specifies the desired behavior, and put conditional code based on this property in the textFieldShouldReturn method. When you instantiate AlertPrompt, set the property as needed.

mklement said...

Apologies - the previous code snippet only works with major iOS version numbers < 10; here's a version for the ages...

NSString *iOsVersion = [[UIDevice currentDevice] systemVersion];
if ([iOsVersion intValue] < 4) {
[self setTransform:CGAffineTransformMakeTranslation(0,130)];
}

Betty said...

> Note: I had to cast the Delegate method as the custom Alert view type so it would know about the textField property.

Please *SHOW* us the code... not just say "I did it".

Hire iphone developer said...

Incredible. The genuine iPhone developer for iPhone applications development should be able to provide you the professional services.

iphone developer

h4ns said...

What youre saying is completely true. I know that everybody must say the same thing, but I just think that you put it in a way that everyone can understand. I also love the images you put in here. They fit so well with what youre trying to say. Im sure youll reach so many people with what youve got to say.

Arsenal vs Huddersfield Town live streaming
Arsenal vs Huddersfield Town live streaming
Wolverhampton Wanderers vs Stoke City Live Streaming
Wolverhampton Wanderers vs Stoke City Live Streaming
Notts County vs Manchester City Live Streaming
Notts County vs Manchester City Live Streaming
Bologna vs AS Roma Live Streaming
Bologna vs AS Roma Live Streaming
Juventus vs Udinese Live Streaming
Juventus vs Udinese Live Streaming
Napoli vs Sampdoria Live Streaming
Napoli vs Sampdoria Live Streaming
Fulham vs Tottenham Hotspur Live Streaming
Fulham vs Tottenham Hotspur Live Streaming
AS Monaco vs Marseille Live Streaming
AS Monaco vs Marseille Live Streaming
Alajuelense vs Perez Zeledon Live Streaming
Alajuelense vs Perez Zeledon Live Streaming
Technology News | News Today | Live Streaming TV Channels

Hire iphone developer said...

Hi
The genuine iPhone developer for iPhone applications development should be able to provide you the professional services can be obtained when you hire these services for your business,
iphone developer

Alex said...

Is there any way to change the keyboard type to numeric when the alert pops up?

Crazy Data said...

"message" is not showed when "message" is short.

Add and Fix code in initWithTitle.

NSString *msg = [ NSString stringWithFormat:@"\n%@", message ];
if (self = [super initWithTitle:title message:msg delegate:delegate cancelButtonTitle:cancelButtonTitle otherButtonTitles:okayButtonTitle, nil])

u will show "message"!

Stuart said...

To solve the wait_fences: failed to receive reply: 10004003 error, instead of introducing a delay in making the text field the first responder, simply calling [super show] first did the trick for me.
- (void)show
{
[super show];
[textField becomeFirstResponder];
}

Sean and Jennie said...

This code really helped me out! Thanks

tfforums said...

IOS7 version?