By Stian Høiland


2013-11-04 08:53:45 8 Comments

I'm trying to achieve similar positioning behavior as the bottom text input bar in Apple's Messages app.

I have tried many approaches, searched high and low and there are many similar questions but none have been satisfactory.

To specify:

  1. There is a UIToolbar at the bottom of the view
  2. The toolbar is to follow the keyboard as the keyboard appears and disappears
  3. The toolbar should stay on top of the keyboard when the keyboard is visible
  4. When the keyboard is hidden, the toolbar stays ("docked") at the bottom of the view

The proposed solutions:

Manually animate the toolbar in response to keyboard appearance notifications

This solution does not meet a special case of the second requirement (the toolbar is to follow the keyboard as the keyboard appears and disappears):

  • In iOS 7, UIScrollViewKeyboardDismissMode was introduced. It enables an interactive gesture for dismissing the keyboard. As the the user pans past the top edge of the keyboard, the keyboard frame gradually follows. This solution does nothing to accomodate this behavior and just leaves the toolbar stranded at it's animated position.

In addition, this solution also fails to fulfil a special case of the third requirement (the toolbar should stay on top of the keyboard when the keyboard is visible):

  • Rotation. This solution calls for additional, annoyingly extraneous code (as we will see in the next proposed solution) to rotate the toolbar in response to device rotation.

Another issue with this solution:

  • Keyboard height. With this solution, the toolbar it is not assumed to be part of the height of the keyboard, so additional code must be written to support proper insetting of content.

Next proposed solution:

Use UIResponder's inputAccessoryView

This solution seems to be the way Apple intended to support this kind of behavior, as it solves all the shortcomings of manually animating the toolbar. But this solution completely misses the fourth requirement (when the keyboard is hidden, the toolbar stays ("docked") at the bottom of the view).


It seems the solution is to use UIResponder's inputAccessoryView, but somehow making the inputAccessoryView not move below the view. I'm looking for clean code to accomplish this. There are elaborate, almost noble, attempts elsewhere, but as mentioned, they do not meet the requirements.

In my particular case, I am looking to use UINavigationController's toolbar which entails additional issues as this is not intended behavior for UINavigationController. No matter, I'm willing to introduce some hacky fixes to accomplish that.

5 comments

@jonprasetyo 2015-06-25 17:24:26

For those looking for Swift version:

Connect your toolbar (in my case 'myToolBar') on to your view controller. Then override canBecomeFirstResponder method and override the getter inputAccessoryView variable. Also don't forget to add the self.myToolBar.removeFromSuperview() or else xcode will complain.

class ViewController: UIViewController {

    @IBOutlet var myToolBar: UIToolbar!

    override func canBecomeFirstResponder() -> Bool {
        return true
    }


    override var inputAccessoryView:UIView{
        get{
            return self.myToolBar
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        self.myToolBar.removeFromSuperview()
    }
}

@DazChong 2016-04-29 11:48:58

OMG, you simplified into absolute minimal!

@jonprasetyo 2016-05-12 08:23:40

In the end i didn't use this code i went with github.com/slackhq/SlackTextViewController much easier no hassle lol

@DazChong 2016-05-13 02:42:37

I tested with the STVC, but I dislike to add so much bloat of codes just for a basic TextView, and the STVC is not fully adapt to storyboard, need to use XIB. I most disappointed it is not an actual accessories bar view, it doesn't adapt to interactive dismiss as in Whatsapp / iMessage. I still yet to find the better solution. But worth checking out github.com/muukii/NextGrowingTextView if all u need is 100% storyboard compatible for simple input TextView. But still, this one not adapt to accessoriesbar view either..

@jonprasetyo 2016-09-21 03:44:10

@DazChong Thanks for sharing

@Jonathan Badeen 2014-05-27 03:23:28

I was just shown "the" solution by Jason Foreman (@threeve). On your view controller (yes, view controller) add inputAccessoryView: and return the view you want to dock at the bottom and move with the keyboard. It just works. The view doesn't actually need to be in your view hierarchy it will be inserted by the view controller automatically.

edit: also implement canBecomeFirstResponder and return YES (as noted by Max Seelemann). reloadInputViews can be handy too.

@Max Seelemann 2014-05-27 09:51:03

You should add that the view controller also needs to return YES from -canBecomeFirstResponder and need to make itself the first responder. But after that -- just magic. +100 ;)

@Stian Høiland 2014-05-27 19:13:54

Sounds interesting, but I don't quite understand. Care to elaborate?

@Stian Høiland 2014-05-27 19:21:26

Wait... I think I understand (I'll test in code): The UIViewController should return a inputAccessoryView, instead of, for example, the UITextView or UITextField.

@Stian Høiland 2014-05-27 19:39:41

Yep, fantastic, this works. It also makes sense semantically. D'oh! Thanks for sharing!

@Stian Høiland 2014-05-27 20:54:19

This also works for the UIToolbar managed by UINavigationController.

@Cabus 2014-06-01 00:51:36

Why are features like this so poorly documented ? Is there a repository or something where undocumented finds like this are collected ? (UINavigationBars can also be popped sideways by calling 'setHidden' in the right places in viewWillAppear, etc.) <- stuff like that ! )

@Rick 2014-07-17 12:16:52

Would be good to see a complete code example for this "magic" solution.

@Gui Moura 2014-10-03 03:50:06

The proposed solution generate a retain cycle between the UIViewController and inputView. Maybe it's a bug from iOS SDK, but it causes the UIViewController to never get released, and generating leaks.

@Tom Gilder 2014-11-11 16:33:37

Sadly I can't get this technique working in conjunction with a UITabBar. The inputAccessoryView is just drawn over the tabs.

@devios1 2015-05-26 01:37:33

I must be missing something. Neither inputAccessoryView nor canBecomeFirstResponder are being called on my view controller when it is first shown. Am I supposed to add the input view to the view manually as well as returning it from inputAccessoryView?

@devios1 2015-05-26 16:56:52

I got it working by calling [self becomeFirstResponder] in viewDidLoad of all things.

@Can Poyrazoğlu 2015-06-12 12:01:00

I am getting *** Terminating app due to uncaught exception 'UIViewControllerHierarchyInconsistency', reason: 'child view controller:<UICompatibilityInputViewController: 0x159ec54b0> should have parent view controller:<ULPostViewController: 0x157dec330> but requested parent is:<UIInputWindowController: 0x1580f0800>' error with this method. (iOS 8.3)

@Jason 2015-08-17 20:34:21

@CanPoyrazoğlu I think this might occur if you've attempted to add the "accessory" view to the main view controller we're working in. You don't need to manually add it when using as an inputAccessoryView.

@jasongregori 2015-08-22 02:09:41

I can't believe this works. This is amazing! Great find!

@Alex Soble 2014-10-20 14:00:41

There's an excellent, easy-to-implement, open-source solution to this from the good folks at Slack: SlackTextViewController.

Here's how to implement a view with a docked toolbar in four steps:

  1. Install the pod into your project. (http://cocoapods.org/?q=SlackTextViewController)
  2. If you're writing an app in Swift, create a header to bridge between your Swift code and their Obj-C classes. Here's a nice quick walkthrough on that. (The bridging header won't be necessary once the classes are translated into Swift, anyone want to collaborate on that?)
  3. Create a MessageViewController that inherits from SLKTextViewController, no need to write any more code than this:

    import Foundation
    import UIKit
    
    class MessageViewController: SLKTextViewController {
    
        required init(coder aDecoder: NSCoder!) {
            super.init(coder: aDecoder)
        }
    }
    
  4. In Storyboard, create a View Controller that inherits from MessageViewController.
  5. Test the app on a device or emulator, you'll see a beautiful docked textbar that also (as a bonus) expands as the user writes additional lines of text.

Props to the team at Slack for extracting such a useful pod.

@DonnaLea 2014-10-25 04:50:55

Thanks for the tip on the SlackTextViewController!

@jonprasetyo 2015-06-22 17:50:30

Nice and quick - cool library

@Russ Hooper 2014-08-08 22:37:53

Jonathan Badeen's aforementioned solution worked for me. Here's code from Arik showing how to implement it (this should go in your appropriate view controller):

    - (BOOL)canBecomeFirstResponder{

        return YES;

    }

    - (UIView *)inputAccessoryView{

        return self.inputAccessoryToolbar;  
     // where inputAccessoryToolbar is your keyboard accessory view

    }

@kubbing 2015-02-23 15:19:44

wow, works! you are the best

@David Alejandro Londoño Mejía 2016-04-08 19:59:53

problem there when trying to go to a diferent controller, by push or present, the inputAccessoryView still shows

@daltoniam 2013-12-24 05:11:28

So I know this is an old post and I am not sure if you resolved this or not, but I found a way to get this working. I believe there is a bug in the inputAccessoryView, but I created a hacky solution to behave the way the messages app does. I think I provided all the necessary code to implement the work around. I am going to try and get a more appropriate blog post created sometime in the near future, with a more in depth description with my findings. Any questions, let me know.

@property(nonatomic,assign)BOOL isFirstKeyboard; //workaround for keyboard bug

@property(nonatomic,assign)BOOL isViewAppear;

@property(nonatomic,strong)ChatBarView *chatView; //custom chat bar view

@property(nonatomic,strong)UIView *footerPadView; //just to add some nice padding
////////////////////////////////////////////////////////////////////////////////////////////////////
//in the view did load
- (void)viewDidLoad
{
    //more app specific code...
    self.tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeInteractive;
    self.chatView.textView.inputAccessoryView = self.chatView;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
-(void)done
{
    self.isFirstKeyboard = YES;
    [self.chatView.textView becomeFirstResponder];
}
////////////////////////////////////////////////////////////////////////////////////////////////////
- (void) moveTextViewForKeyboard:(NSNotification*)aNotification up:(BOOL)up
{
    if(!self.isViewAppear)
        return;
    //NSLog(@"keyboard is: %@", up ? @"UP" : @"Down");
    if(!up && !self.isFirstKeyboard)
        [self performSelector:@selector(done) withObject:nil afterDelay:0.01];
    else if(!up & self.isFirstKeyboard)
    {
        self.isFirstKeyboard = NO;
        [self.view addSubview:self.chatView];
        CGRect frame = self.chatView.frame;
        frame.origin.y = self.view.frame.size.height - self.chatView.frame.size.height;
        self.chatView.frame = frame;
    }
    else
    {
        NSDictionary* userInfo = [aNotification userInfo];
        NSTimeInterval animationDuration;
        UIViewAnimationCurve animationCurve;
        CGRect keyboardEndFrame;

        [[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:&animationCurve];
        [[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&animationDuration];
        [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] getValue:&keyboardEndFrame];

        // Animate up or down
        [UIView beginAnimations:nil context:nil];
        if(up)
            [UIView setAnimationDuration:0.2];
        else
            [UIView setAnimationDuration:0.3];
        [UIView setAnimationCurve:animationCurve];

        CGRect frame = self.footerPadView.frame;
        CGRect keyboardFrame = [self.view convertRect:keyboardEndFrame toView:nil];
        if (up)
            frame.size.height = keyboardFrame.size.height - self.chatView.frame.size.height;
        else
            frame.size.height = 0;
        self.footerPadView.frame = frame;
        self.tableView.tableFooterView = self.footerPadView;
        [UIView commitAnimations];
    }
}
////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)keyboardWillShow:(NSNotification *)aNotification {
    [self moveTextViewForKeyboard:aNotification up:YES];
}
////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)keyboardWillHide:(NSNotification *)aNotification
{
    [self moveTextViewForKeyboard:aNotification up:NO];
}
////////////////////////////////////////////////////////////////////////////////////////////////////

Related Questions

Sponsored Content

2 Answered Questions

[SOLVED] Toolbar disappears when hiding keyboard

5 Answered Questions

[SOLVED] Leaving inputAccessoryView visible after keyboard is dismissed

1 Answered Questions

inputAccessoryView dismissed when UIAlertController displayed

8 Answered Questions

[SOLVED] How to hide inputAccessoryView without dismissing keyboard

5 Answered Questions

[SOLVED] UIView atop the Keyboard similar to iMessage App

1 Answered Questions

Can I dock UIToolbar on UIPickerview like docked on Keyboard

2 Answered Questions

1 Answered Questions

Sponsored Content