Skip to content

How to add your own Done button to the iPhone numeric keypad

Last revised 22nd September 2010 – as I missed some code from this article.

There are several issues which you must be aware of when working around the missing done button on the iPhone numeric keypad. Use of private API’s is frowned upon by apple and they can reject your application. Also a lot of the solutions out there don’t work very well for OS4. This solution works by drawing a button on top of the keypad and animating it off the screen when required. It also draws the text so you don’t have to rely on images.

Firstly add a header file call it something like NumberKeypadModController.h also create an M file. Add the follow code to your H file.

#import <Foundation/Foundation.h>

@protocol NumberKeypadModControllerDelegate <NSObject>

– (void) donePressed:(id)sender;

@end

/**
*    The class used to create the keypad
*/
@interface NumberKeypadModController : NSObject
{
NSTimer *fixKeyboardTimer;
UIButton *doneButton;
UITextField *currentTextField;
id<NumberKeypadModControllerDelegate> delegate;
int        showHideCounter;
BOOL    doneButtonShown;
BOOL    doneButtonShownRecently;
}

@property (nonatomic, retain) NSTimer *fixKeyboardTimer;

@property (nonatomic, retain) UIButton *doneButton;
@property (nonatomic, retain) UITextField *currentTextField;

@property (nonatomic, retain) id<NumberKeypadModControllerDelegate> delegate;

– (void)textFieldShouldBeginEditing:(UITextField *)textField;
– (void)textFieldDidBeginEditing:(UITextField *)textField;
– (void)textFieldShouldEndEditing:(UITextField *)textField;

– (void)resignedResponderWithView:(UIView*)textField;

@end

And your M file

#import “NumberKeypadModController.h”

@interface NumberKeypadModController (PrivateMethods)
– (void) addDoneToKeyboard;
– (void) removeDoneFromKeyboard;
– (void) donePressed;
@end

#define PRE_SLIDING_DELAY_DURATION        0.1
#define SLIDE_IN_ANIMATION_DURATION    0.2
#define SLIDE_OUT_ANIMATION_DURATION    0.15

#define USE_TEXTURED_BUTTON        0

@implementation NumberKeypadModController

@synthesize fixKeyboardTimer;

@synthesize doneButton;
@synthesize currentTextField;

@synthesize delegate;

– (id)init {
if (self = [super init]) {
self.fixKeyboardTimer = nil;
#if (USE_TEXTURED_BUTTON != 0)
self.doneButton = [UIButton buttonWithType:UIButtonTypeCustom];
doneButton.frame = CGRectMake(0, 480, 105, 53);
if ([[[UIDevice currentDevice] systemVersion] hasPrefix:@”3″]) {
doneButton.frame = CGRectMake(0, 480, 105, 53);
[doneButton setImage:[UIImage imageNamed:@”DoneUp3.png”] forState:UIControlStateNormal];
[doneButton setImage:[UIImage imageNamed:@”DoneDown3.png”] forState:UIControlStateHighlighted];
} else {
[doneButton setImage:[UIImage imageNamed:@”DoneUp.png”] forState:UIControlStateNormal];
[doneButton setImage:[UIImage imageNamed:@”DoneDown.png”] forState:UIControlStateHighlighted];
}
#else
self.doneButton = [UIButton buttonWithType:UIButtonTypeCustom];
doneButton.frame = CGRectMake(0, 480, 105, 53);
if ([[[UIDevice currentDevice] systemVersion] hasPrefix:@”3″])
doneButton.frame = CGRectMake(0, 480, 105, 53);
doneButton.titleLabel.font = [UIFont boldSystemFontOfSize:18];
[doneButton setTitleColor:[UIColor colorWithRed:60.0f/255.0f green:64.0f/255.0f blue:73.0f/255.0f alpha:1.0] forState:UIControlStateNormal];
[doneButton setBackgroundImage:[UIImage imageNamed:@”doneKeyDownBackground.png”] forState:UIControlStateHighlighted];
[doneButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted];
[doneButton setTitle:@”DONE” forState:UIControlStateNormal];
#endif

[doneButton addTarget:self action:@selector(donePressed) forControlEvents:UIControlEventTouchUpInside];

self.currentTextField = nil;
self.delegate = nil;

showHideCounter = 0;
doneButtonShown = NO;
}
return self;
}

– (void)textFieldShouldBeginEditing:(UITextField *)textField {
if (textField.keyboardType != UIKeyboardTypeNumberPad)
{
doneButton.hidden = YES;
doneButtonShownRecently = YES;
}
}

– (void)textFieldDidBeginEditing:(UITextField *)textField {
if (textField.keyboardType != UIKeyboardTypeNumberPad)
return;

//    NSLog(@”textFieldDidBeginEditing”);
self.currentTextField = textField; //Save reference to current textfield being edited

if (!doneButtonShownRecently)
{
if (fixKeyboardTimer)
[fixKeyboardTimer invalidate];
self.fixKeyboardTimer = [NSTimer timerWithTimeInterval:PRE_SLIDING_DELAY_DURATION target:self
selector:@selector(addDoneToKeyboard) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:fixKeyboardTimer forMode:NSDefaultRunLoopMode];
} else
{
[doneButton removeFromSuperview];
[self addDoneToKeyboard];
}
}

– (void)textFieldShouldEndEditing:(UITextField *)textField {
if (textField.keyboardType != UIKeyboardTypeNumberPad)
{
doneButtonShownRecently = YES;
[self performSelector:@selector(considerDoneButtonReallyHidden) withObject:nil afterDelay:SLIDE_OUT_ANIMATION_DURATION];
return;
}
[self removeDoneFromKeyboard];
}

– (void) textFieldDidEndEditing:(UITextField *)textField {
}

– (void)resignedResponderWithView:(UIView*)textField {
//    NSLog(@”resignedResponderWithView”);
[self removeDoneFromKeyboard];
}

– (void) addDoneToKeyboard {
//    NSLog(@”addDoneToKeyboard”);

doneButton.hidden =  NO;

//Add a button to the top, above all windows
NSArray *allWindows = [[UIApplication sharedApplication] windows];
NSUInteger topWindowIndex = [allWindows count] – 1;
UIWindow *topWindow = [allWindows objectAtIndex:topWindowIndex];

// check if top window is of keypad or else
NSString *topViewClassName = [NSString stringWithFormat:@”%@”, [topWindow class]];
while (![topViewClassName isEqualToString:@”UITextEffectsWindow”] ) {
–topWindowIndex;

if(topWindowIndex < 0)
break;

topWindow = [allWindows objectAtIndex:topWindowIndex];
topViewClassName = [NSString stringWithFormat:@”%@”, [topWindow class]];
}

if(topWindowIndex < 0) {
topWindowIndex = [allWindows count] – 1;
topWindow = [allWindows objectAtIndex:topWindowIndex];
}

if (doneButton.superview)
[doneButton removeFromSuperview];

[topWindow addSubview:doneButton];

if (!doneButtonShownRecently) {
[UIView beginAnimations:nil context:nil];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:SLIDE_IN_ANIMATION_DURATION];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
doneButton.frame = CGRectMake(0, 480-53, doneButton.frame.size.width, 53);
[UIView commitAnimations];
} else {
doneButton.frame = CGRectMake(0, 427, doneButton.frame.size.width, 53);
}

doneButtonShown = YES;
}

– (void) removeDoneFromKeyboard {
//    NSLog(@”removeDoneFromKeyboard”);

[fixKeyboardTimer invalidate];
self.fixKeyboardTimer = nil;

if (!doneButtonShownRecently) {
[UIView beginAnimations:nil context:nil];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:SLIDE_OUT_ANIMATION_DURATION];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
doneButton.frame = CGRectMake(0, 480, doneButton.frame.size.width, 53);
[UIView commitAnimations];
} else {
doneButton.frame = CGRectMake(0, 480, doneButton.frame.size.width, 53);
}

doneButtonShown = NO;
doneButtonShownRecently = YES;
[self performSelector:@selector(considerDoneButtonReallyHidden) withObject:nil afterDelay:SLIDE_OUT_ANIMATION_DURATION];
}

– (void)considerDoneButtonReallyHidden {
//    NSLog(@”considerDoneButtonReallyHidden”);
doneButtonShownRecently = NO;
}

– (void) donePressed {
[self.currentTextField resignFirstResponder];
if ([delegate respondsToSelector:@selector(donePressed:)])
[delegate performSelector:@selector(donePressed:) withObject:self.currentTextField];
}

– (void) dealloc {
[fixKeyboardTimer release];
[doneButton removeFromSuperview];
[doneButton release];
[delegate release];
[super dealloc];
}

@end

Now to your view header file add an import to NumberKeypadModController.h

Now its imple matter of adding a delegate to your interface and a property on the view your going to use the code. In your viewdidload add this code.

@interface YourViewController : UIViewController <UITextFieldDelegate, NumberKeypadModControllerDelegate> {

NumberKeypadModController *numberKeyPadModController;
IBOutlet UITextField *textFieldRow1;
}

@property(nonatomic,retain) NumberKeypadModController *numberKeyPadModController;
@property(nonatomic,retain) UITextField *textFieldRow1;

@end

And your m file.

#import “YourViewController.h”
#import “NumberKeypadModController.h”

@interface UIView (FindAndResignFirstResponder)
– (UIView*)findFirstResponder;
@end
@implementation UIView (FindAndResignFirstResponder)
– (UIView*)findFirstResponder {
if (self.isFirstResponder) {
[self resignFirstResponder];
return self;
}
for (UIView *subView in self.subviews) {
if ([subView findFirstResponder])
return subView;
}
return nil;
}
@end

@implementation YourViewController

@synthesize numberKeyPadModController;
@synthesize textFieldRow1;

– (void)viewDidLoad {
[super viewDidLoad];

self.numberKeyPadModController = [[[NumberKeypadModController alloc] init] autorelease];
numberKeyPadModController.delegate = self;

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(doneButton:) name:@”DoneButtonPressed” object:nil];

textFieldRow1.delegate = self;
}

– (void)doneButtonPressed:(UITextField*)sender{
if ([textFieldRow1 isEditing]) {
[textFieldRow1 resignFirstResponder];
}
}

– (void) donePressed:(id)sender {
UIView* firstResponder = [self.view findFirstResponder];
[firstResponder resignFirstResponder];
}

– (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
[numberKeyPadModController textFieldShouldBeginEditing:textField];

return YES;
}

– (void) textFieldDidBeginEditing:(UITextField *)textField {
[numberKeyPadModController textFieldDidBeginEditing:textField];
}

– (BOOL) textFieldShouldEndEditing:(UITextField *)textField {
[numberKeyPadModController textFieldShouldEndEditing:textField];
return YES;
}

-(void)touchesBegan :(NSSet *)touches withEvent:(UIEvent *)event {
UIView* firstResponder = [self.view findFirstResponder];
[firstResponder resignFirstResponder];
[numberKeyPadModController resignedResponderWithView:firstResponder];

[super touchesBegan:touches withEvent:event];
}

– (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}

– (void)viewDidUnload {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}

– (void)dealloc {
[super dealloc];
}

@end

Now in interface builder add a UITextField and assign the outlet, save and run.

I`m currently looking at making a decimal point version of this. I have seen others of the web, but the animation isn`t as good as mine. 😉

If you find this code helpful please consider buying a copy of our app. Suggestions and feedback our also welcome.

iTunes page Speaking Times Table iPhone App

Speaking Times Tables - Available on the App Store

I’d be happy to talk further about the code, please leave your comments below, you’ll notice from our app that we also use a bar above the keypad, which I haven’t included in the above code.

ALSO LET ME KNOW IF YOU HAVE ANY PROBLEMS WITH THIS CODE !!

by JM

Similar Posts:

Post a Comment

You must be logged in to post a comment.