Some apps – like Twinkle e.g. – add a custom toolbar on top of the existing keyboard mostly to add "send" or "done" buttons. These toolbars animate in from the bottom along with the keyboard.
The next trick is actually finding the keyboard view. The keyboard view is actually in a second UIWindow separate from your app's main window. So you need to write code to walk all of the windows in the app. Then for each window you need to find the one that has a subview containing the keyboard. I do this by finding the subview whose description contains the string 'UIKeyboard'. I do this to avoid compiler warnings because the real class for the keyboard view isn't exposed via the SDK. But all you need is a UIView reference anyway.
Once you get the keyboard view, along with the coordinates you got earlier, you can now add a subview to the keyboard view.
I'm not going to give all the code because I worked very hard to figure this out on my own and it gives my app a little edge. Hopefully the info I gave will get you far enough to figure the details out.
I stumbled upon this post and found it very helpful.
@RickMaddy, I'm trying to do something similar to what you've described, but instead of adding a UILabel, I'm trying to add a UIToolbar and a UIBarButtonItem that the user can click. I've been able to add the toolbar and the button, but my problem is, the toolbar button is only able to be clicked if it's on TOP of the keyboard. If I add the toolbar as a subview ABOVE the keyboard, then the button cannot be clicked. Here's the relevant code snippet from the keyboardWillShow: method. (Thanks to both RickMaddy and keyboardcowboy for the posts. Much of the code below is taken from their posts.)
Code:
- (void)keyboardWillShow:(NSNotification *)note
{
NSDictionary *info = [note userInfo];
NSValue *keyBounds = [info objectForKey:UIKeyboardBoundsUserInfoKey];
CGRect bndKey;
[keyBounds getValue:&bndKey];
UIToolbar *toolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, -40, bndKey.size.width, 40)];
UIBarButtonItem *barButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Test button" style:UIBarButtonItemStyleBordered target:self action:@selector(buttonClicked:)];
NSArray *items = [[NSArray alloc] initWithObjects:barButtonItem, nil];
[toolbar setItems:items];
[items release];
UIWindow* tempWindow = [[[UIApplication sharedApplication] windows] objectAtIndex:1];
UIView* keyboard;
for(int i = 0; i < [tempWindow.subviews count]; i++)
{
//Get a reference of the current view
keyboard = [tempWindow.subviews objectAtIndex:i];
//Check to see if the description of the view we have referenced is "UIKeyboard" if so then we found
//the keyboard view that we were looking for
if([[keyboard description] hasPrefix:@"<UIKeyboard"] == YES)
{
[keyboard addSubview:toolbar];
}
}
}
- (void)buttonClicked:(NSNotification *)note
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"You clicked the keyboard button!" message:@"Hey! You clicked the button on top of the keyboard." delegate:self cancelButtonTitle:@"Yep" otherButtonTitles:nil];
[alert show];
[alert release];
}
With the above code, the toolbar is added just above the keyboard, and there's a button labeled "Test button" on the toolbar. However, it doesn't accept user interaction. BUT if you change the code where the toolbar is created to the following (keep everything else the same):
I'm not positive but I think the problem lies with the fact that the button is actually located with a bounding box outside of it's parent's (grandparent's actually) view. I saw this once before. Buttons don't get touch events if their bounding box is outside the frame of their parent view.
I've never tried this, and it may make the code even more fragile to future changes by Apple, but you might be able to resize and reposition the keyboard view so it extends high enough so you can place the toolbar within its frame. Yuck.
Ooh, I just had an idea that might work. Add the toolbar with the negative offset so it animates and appears in the correct position. Then after you get the 'keyboard did appear' event, change the toolbar's superview to the window or some other, bigger view. When you get the event 'keyboard will hide', put it back so the superview is the keyboard again so it properly animates out of view.
Great suggestion! You were right on. On the keyboardDidShow: method, I removed the toolbar from the keyboard's view and added it as a subview to the UIWindow and now it works great! Your help is much appreciated.
This thread got me started, so here's my solution. As with the intial approach on this thread I retrieve the keyboard from the notification. I then take it one step further. After adding my UIToolbar to the keyboard I rebound the keyboard view. So the toolbar and they keyboard will both collect touch events within the Keyboard class frame. Handles rotation as well.
Code:
- (void)keyboardWillShow:(NSNotification *)notification
{
for (UIWindow *keyboardWindow in [[UIApplication sharedApplication] windows]) {
// Now iterating over each subview of the available windows
for (UIView *keyboard in [keyboardWindow subviews]) {
// Check to see if the description of the view we have referenced is UIKeyboard.
// If so then we found the keyboard view that we were looking for.
if([[keyboard description] hasPrefix:@"<UIKeyboard"] == YES) {
NSValue *v = [[notification userInfo] valueForKey:UIKeyboardBoundsUserInfoKey];
CGRect kbBounds = [v CGRectValue];
if(keyboardToolbar == nil) {
keyboardToolbar = [[UIToolbar alloc] initWithFrame:CGRectZero];
keyboardToolbar.barStyle = UIBarStyleBlackTranslucent;
UIBarButtonItem *barButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Dismiss" style:UIBarButtonItemStyleBordered target:self action:@selector(dismissKeyboard:)];
NSArray *items = [[NSArray alloc] initWithObjects:barButtonItem, nil];
[keyboardToolbar setItems:items];
[items release];
}
[keyboardToolbar removeFromSuperview];
keyboardToolbar.frame = CGRectMake(0, 0, kbBounds.size.width, 30);
[keyboard addSubview:keyboardToolbar];
keyboard.bounds = CGRectMake(kbBounds.origin.x, kbBounds.origin.y, kbBounds.size.width, kbBounds.size.height + 60);
for(UIView* subKeyboard in [keyboard subviews]) {
if([[subKeyboard description] hasPrefix:@"<UIKeyboardImpl"] == YES) {
subKeyboard.bounds = CGRectMake(kbBounds.origin.x, kbBounds.origin.y - 30, kbBounds.size.width, kbBounds.size.height);
}
}
}
}
}
}
Maybe I'm doing something wrong but with the code above, I have noticed that it adds multiple toolbars one over the other and soon enough the Dismiss button doesn't work. If I use it on one tab view then it's fine but the problem is I use it on muliple tabs which is why it keeps adding I think. If anyone knows how to resolve this, please share.
Having not visited this topic in quite some time here is a brief update...
This code is working in my demo to this point. I setup a Messages type app for educational purposes. Table datasource and delegates not provided. Obviously the messages array isn't provided either.
Notes based on comments:
1.) You have to make sure you don't keep stacking controls on top of the keyboard. As you can see in keyboardDidHide method that I remove the text field from the keyboard and apply it back to the bottom of the tableview. And vice versa when when the keyboard is called again.
2.) Obviously this is not all the code but enough to make it work. Types are implied by what is being instantiated. If there is no type defined obviously it's defined in the header.
3.) IMPORTANT! Have not checked the code for memory leaks. Use at your own discretion.
Code:
@synthesize tableView;
@synthesize textField;
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.separatorColor = [UIColor clearColor];
self.tableView.backgroundColor = [UIColor colorWithRed:(0xDD/255.0) green:(0xE2/255.0) blue:(0xEB/255.0) alpha:1.0];
self.view.backgroundColor = [UIColor colorWithRed:(0xDD/255.0) green:(0xE2/255.0) blue:(0xEB/255.0) alpha:1.0];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:self.view.window];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:self.view.window];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidShow:) name:UIKeyboardDidShowNotification object:self.view.window];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidHide:) name:UIKeyboardDidHideNotification object:self.view.window];
}
const int TEXTFIELD_HEIGHT = 30;
- (void) viewWillAppear:(BOOL)animated {
if(self.textField == nil) {
self.textField = [[KeyboardTextField alloc] initWithFrame:CGRectZero];
self.textField.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"border.png"]];
self.textField.delegate = self;
self.textField.returnKeyType = UIReturnKeySend;
[self.view addSubview:self.textField];
self.textField.frame = CGRectMake(0, self.view.frame.size.height - TEXTFIELD_HEIGHT, self.view.frame.size.width, TEXTFIELD_HEIGHT);
}
NSString *dummyData = [[NSBundle mainBundle] pathForResource:@"dummy" ofType:@"plist"];
messages = [[NSArray arrayWithContentsOfFile:dummyData] retain];
if(messages.count > 0) {
NSDictionary* dictionary = [messages objectAtIndex:0];
self.title = [dictionary objectForKey:@"sender"];
}
[self.tableView reloadData]; // manually sync datasource so we can scroll to the bottom
}
- (void) viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self scrollToBottom];
}
- (void)keyboardWillShow:(NSNotification *)notification {
for (UIWindow *keyboardWindow in [[UIApplication sharedApplication] windows]) {
// Now iterating over each subview of the available windows
for (UIView *keyboard in [keyboardWindow subviews]) {
// Check to see if the description of the view we have referenced is UIKeyboard.
// If so then we found the keyboard view that we were looking for.
if([[keyboard description] hasPrefix:@"<UIKeyboard"] == YES) {
NSValue *v = [[notification userInfo] valueForKey:UIKeyboardBoundsUserInfoKey];
CGRect kbBounds = [v CGRectValue];
keyboard.bounds = CGRectMake(kbBounds.origin.x, kbBounds.origin.y, kbBounds.size.width, kbBounds.size.height + (TEXTFIELD_HEIGHT * 2));
textField.frame = CGRectMake(0, 0, kbBounds.size.width, TEXTFIELD_HEIGHT);
[textField removeFromSuperview];
[keyboard addSubview:textField];
for(UIView* subKeyboard in [keyboard subviews]) {
if([[subKeyboard description] hasPrefix:@"<UIKeyboardImpl"] == YES) {
keyboardBounds = CGRectMake(kbBounds.origin.x, kbBounds.origin.y - TEXTFIELD_HEIGHT, kbBounds.size.width, kbBounds.size.height);
subKeyboard.bounds = keyboardBounds;
}
}
}
}
}
}
- (void)keyboardDidShow:(NSNotification*)notification {
self.tableView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height - keyboardBounds.size.height - TEXTFIELD_HEIGHT);
NSIndexPath* indexPath = [NSIndexPath indexPathForRow:messages.count - 1 inSection:0];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
- (void)keyboardWillHide:(NSNotification*)notification {
textField.text = nil;
}
- (void)keyboardDidHide:(NSNotification*)notification {
[textField removeFromSuperview];
textField.text = nil;
[self.view addSubview:textField];
textField.frame = CGRectMake(0, self.view.frame.size.height - TEXTFIELD_HEIGHT, textField.frame.size.width, textField.frame.size.height);
self.tableView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height - TEXTFIELD_HEIGHT);
[self scrollToBottom];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[self.textField resignFirstResponder];
return NO;
}
- (void) scrollToBottom {
NSIndexPath* indexPath = [NSIndexPath indexPathForRow:messages.count - 1 inSection:0];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
Here is the link to the implementation..... I have used this logic to my forms and added next and previous button too.....Add Tool bar to keyboard
While working on this method further I came across some issue, I had som textfields where I dont want the toolbar,there also i am getting the keyboard with the toolbar. It seems once created it is not going away. Ny idea how to do fix this issue?
-John
Last edited by JohnMabassa; 08-05-2009 at 07:51 AM.
Here is the link to the implementation..... I have used this logic to my forms and added next and previous button too.....Add Tool bar to keyboard
While working on this method further I came across some issue, I had som textfields where I dont want the toolbar,there also i am getting the keyboard with the toolbar. It seems once created it is not going away. Ny idea how to do fix this issue?
-John
it seems the easiest way in my head would be to make a BOOL = YES whenever one of your textFields is pressed.
under the didStartEditing (if that's what it's called, I forget) make your BOOL yes.
then surround all your code to show the toolbar with an if clause...
keyboardDidShow {
if (myBOOL == YES) {
//do all your code to show the toolbar here
}
}
under didFinishEditing (or whatever it's called) make your BOOL no.
i'll quite possible be trying something very similar quite soon, so i'll post some code example if i do.
if somebody beats me to it, please post your code here!
Hello mates
I know it's a quite old thread...
But I'm having the same problem of Meeper and JohnMabassa...and the code that ziph~~ shared isn't working for me >.>
Can someone help???
hi
I am using both textview and text field in single view controller.
Toolbar is showing fine upon keyboard when clicking in textview but when clicking text field again toolbar is showing upon keyboard. but i don't want toolbar in case of text field.
this is my code
- (void)keyboardWillShowNSNotification *)notification
{
for (UIWindow *keyboardWindow in [[UIApplication sharedApplication] windows]) {
// Now iterating over each subview of the available windows
for (UIView *keyboard in [keyboardWindow subviews]) {
// Check to see if the description of the view we have referenced is UIKeyboard.
// If so then we found the keyboard view that we were looking for.
if([[keyboard description] hasPrefix:@"<UIKeyboard"] == YES) {
NSValue *v = [[notification userInfo] valueForKey:UIKeyboardBoundsUserInfoKey];
CGRect kbBounds = [v CGRectValue];