4

I have an UIToolbar with a TextField and a Button as UIBarButtonItem. I'm trying to use this toolbar as an inputAccessory for the keyboard when the user taps the TextField inside the toolbar.

enter image description here

I found this question that tries to solve the same problem. Unfortunately, the solution was not effective.

What i'm trying is:

class ChatViewController: UIViewController, CLLocationManagerDelegate, UITableViewDelegate, UITableViewDataSource {

    @IBOutlet weak var chatTableView: UITableView!
    @IBOutlet weak var chatToolbar: UIToolbar!

    @IBOutlet weak var textFieldBarButtonItem: UIBarButtonItem!

    override func viewDidAppear(animated: Bool) {
        super.viewDidLoad()
        self.chatTableView.delegate = self
        self.chatTableView.dataSource = self
        self.chatToolbar.removeFromSuperview()

    }

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

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

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{

        let cell = UITableViewCell()
        return cell
    }

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

        return 0
    }

}

And what i'm getting back is:

*** Terminating app due to uncaught exception 'UIViewControllerHierarchyInconsistency', reason: 
'child view controller:<UICompatibilityInputViewController: 0x13ff34e00> should have parent view controller:
<App.ChatViewController: 0x13ff21cc0> but requested parent is:<UIInputWindowController: 0x1400b4600>'

Any ideas?

Community
  • 1
  • 1
adolfosrs
  • 9,286
  • 5
  • 39
  • 67
  • This is not a bad approach, the problem should be that `inputAccessoryView:` is called before `viewDidAppear`. The accessory view should not belong to any view hierarchy. BTW: accepted answer is ok too – Omer Feb 04 '16 at 15:39
  • @Omer Great, and what do you suggest? – adolfosrs Feb 04 '16 at 16:53
  • 1
    I think both approaches are fine, I prefer the accessoryInputView because it's cleaner between other benefits (i.e: easier way of using `UIScrollViewKeyboardDismissModeInteractive`, storyboard/xib independent, constraint madness free =D). The following link contains a obj-c proj with a basic implementation using the accessory view method you can evolve just in case you want to give it a try: https://www.dropbox.com/s/a4bsvz0ie95i3qn/KeyboardAccessory.zip?dl=0 ||| not I"m using a toolbar but you can use any UIView subclass you want – Omer Feb 04 '16 at 17:35
  • I mean, how should I proceed to force the `inputAccessoryView` to be called after the `viewDidAppear`? – adolfosrs Feb 04 '16 at 17:39
  • Not sure why you need that, `inputAccessoryView` is a property of UIResponder (and by that, of UIViewController too). Base implementation of this class uses this property in order to provide custom behaviors if needed. Check the proj I shared. – Omer Feb 04 '16 at 18:22
  • How did you gave perfect constarints to both textfieild and button ? – Abhishek Biswas Dec 21 '17 at 09:41

1 Answers1

11

First things first:

  1. You should create constraints to your Toolbar in Storyboard (left, bottom, right).
  2. Create an outlet to your bottom constraint in your view controller (drag from storyboard). Save initial constraint value (to recover when keyboard disappears)
  3. Create an observer to know when your keyboard appears and disappears (3.1 and create a tap gesture to hide your keyboard)
  4. When keyboard 4.1 appears and 4.2 disappears, you will only change bottom constraint value (keyboard size). You can animate the toolbar too.

Something like:

class ChatViewController: UIViewController, CLLocationManagerDelegate, UITableViewDelegate, UITableViewDataSource {

    @IBOutlet weak var chatTableView: UITableView!
    @IBOutlet weak var chatToolbar: UIToolbar!

    @IBOutlet weak var textFieldBarButtonItem: UIBarButtonItem!

    //2
    @IBOutlet weak var toolbarBottomConstraint: NSLayoutConstraint!
    var toolbarBottomConstraintInitialValue: CGFloat?

    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated)
        self.chatTableView.delegate = self
        self.chatTableView.dataSource = self
        self.chatToolbar.removeFromSuperview()

        //2
        self.toolbarBottomConstraintInitialValue = toolbarBottomConstraint.constant
        //3
        enableKeyboardHideOnTap()

    }

    // 3
    // Add a gesture on the view controller to close keyboard when tapped
    private func enableKeyboardHideOnTap(){

        NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name: UIKeyboardWillShowNotification, object: nil) // See 4.1
        NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name: UIKeyboardWillHideNotification, object: nil) //See 4.2            

        // 3.1
        let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "hideKeyboard")

        self.view.addGestureRecognizer(tap)
    }

    //3.1
    func hideKeyboard() {
        self.view.endEditing(true)
    }

    //4.1
    func keyboardWillShow(notification: NSNotification) {

        let info = notification.userInfo!

        let keyboardFrame: CGRect = (info[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()

        let duration = notification.userInfo![UIKeyboardAnimationDurationUserInfoKey] as! Double        

        UIView.animateWithDuration(duration) { () -> Void in

            self.toolbarBottomConstraint.constant = keyboardFrame.size.height + 5

            self.view.layoutIfNeeded()

        }

    }

    //4.2
    func keyboardWillHide(notification: NSNotification) {

        let duration = notification.userInfo![UIKeyboardAnimationDurationUserInfoKey] as! Double

        UIView.animateWithDuration(duration) { () -> Void in

            self.toolbarBottomConstraint.constant = self.toolbarBottomConstraintInitialValue!
            self.view.layoutIfNeeded()

        }

    }

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

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

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{

        let cell = UITableViewCell()
        return cell
    }

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

        return 0
    }

}

Hope it helps!

thomers
  • 2,603
  • 4
  • 29
  • 50