34

I'm trying to add a uiview to be on top of the keyboard always. I did it first with KeyboardWillShow/Hide, but it dosen't cover all cases and I'm trying to use inputAccesoryView. this is what I tried:

private var accessoryView = UIView(frame: CGRectZero)

class ViewController : UIViewController {

    var myView: customUIView

    override var inputAccessoryView: UIView {
        return accessoryView
    }

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

    override func viewDidLoad() {
       super.viewDidLoad()
       accessoryView = myView
    }
}

I get the following error :

Terminating app due to uncaught exception 'UIViewControllerHierarchyInconsistency', reason: 'child view controller:UICompatibilityInputViewController should have parent view controller:MyViewController but requested parent is: UIInputWindowController: '

any help will be appreciated!

pkamb
  • 33,281
  • 23
  • 160
  • 191
user101010
  • 543
  • 1
  • 5
  • 12
  • Here's a full fledged answer for future viewers: It's not a pod. https://github.com/29satnam/InputAccessoryView – Codetard Sep 27 '18 at 06:54

4 Answers4

59

To get a view to stick above the keyboard, the code itself is pretty simple. The code you posted is not correct, try this (note that you must connect textField to the UITextField in your storyboard):

@IBOutlet weak var textField: UITextField!

override func viewDidLoad() {
    super.viewDidLoad()

    let customView = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: 44))
    customView.backgroundColor = UIColor.red
    textField.inputAccessoryView = customView
}
paulvs
  • 11,963
  • 3
  • 41
  • 66
  • thanks. I wrote this code in viewDidLoad() and I get an error - use of unresolved identifier 'textField' – user101010 Feb 28 '16 at 23:39
  • I clarified the answer a bit @user101010. – paulvs Feb 28 '16 at 23:42
  • just add a random textField to my viewController in storyboard and use it? – user101010 Feb 28 '16 at 23:51
  • 1
    A keyboard can only be displayed if a `UITextField` or `UITextView` is on the screen and gains focus. So you need to connect it to whatever text fields or text views you have on screen. – paulvs Feb 29 '16 at 00:02
  • got it. now I get exactly the same error I mentioned in my question. maybe it has something to with the fact that the textFields I want to connect to are inside a scroll view? – user101010 Feb 29 '16 at 00:26
  • Could it be because the view that you're adding as an input accessory view is already in the view hierarchy? See this http://stackoverflow.com/a/25882277/1305067 for details. – paulvs Feb 29 '16 at 00:33
  • thanks a lot. removeFromSuperview worked. now my final problem is that when the keyboard is down the view disappears. i added observers for keyboard will show and hide. and I want to add the view back to the superview when the keyboard hides and that it will have the same constraints as in the storyboard. any idea? – user101010 Feb 29 '16 at 01:00
  • Oh, so you want your view to always be visible, not only when the keyboard is visible? Like a chat app? Then forget `inputAccessoryView`, it's part of the keyboard. You need to use keyboard notifications to get notified when the keyboard appears and disappears, and the keyboard's size. Then you need to calculate how far to scroll the content view of your scroll view so that your view (which will be at the bottom of the scroll view) is always above the keyboard. See http://stackoverflow.com/questions/3546571/how-to-get-uikeyboard-size-with-apple-iphone-sdk – paulvs Feb 29 '16 at 01:16
  • Hey I followed your way and other answers...but still can't get my view set up correctly...can you please see [this question](http://stackoverflow.com/questions/44008908/inputaccessoryview-is-not-being-added-correctly-to-textview/44008964#440089640) – mfaani May 16 '17 at 19:12
  • Finally a "Hello World" example that works and proves the essentials without all the overkill stuff. Thank you – clearlight Jun 11 '22 at 10:02
33

Details

  • Xcode 11.2 (11B52), Swift 5

Solution

KeyboardToolbarButton

import UIKit

enum KeyboardToolbarButton: Int {

    case done = 0
    case cancel
    case back, backDisabled
    case forward, forwardDisabled

    func createButton(target: Any?, action: Selector?) -> UIBarButtonItem {
        var button: UIBarButtonItem!
        switch self {
            case .back: button = .init(title: "back", style: .plain, target: target, action: action)
            case .backDisabled:
                button = .init(title: "back", style: .plain, target: target, action: action)
                button.isEnabled = false
            case .forward: button = .init(title: "forward", style: .plain, target: target, action: action)
            case .forwardDisabled:
                button = .init(title: "forward", style: .plain, target: target, action: action)
                button.isEnabled = false
            case .done: button = .init(title: "done", style: .plain, target: target, action: action)
            case .cancel: button = .init(title: "cancel", style: .plain, target: target, action: action)
        }
        button.tag = rawValue
        return button
    }

    static func detectType(barButton: UIBarButtonItem) -> KeyboardToolbarButton? {
        return KeyboardToolbarButton(rawValue: barButton.tag)
    }
}

KeyboardToolbar

import UIKit

protocol KeyboardToolbarDelegate: class {
    func keyboardToolbar(button: UIBarButtonItem, type: KeyboardToolbarButton, isInputAccessoryViewOf textField: UITextField)
}

class KeyboardToolbar: UIToolbar {

    private weak var toolBarDelegate: KeyboardToolbarDelegate?
    private weak var textField: UITextField!

    init(for textField: UITextField, toolBarDelegate: KeyboardToolbarDelegate) {
        super.init(frame: .init(origin: .zero, size: CGSize(width: UIScreen.main.bounds.width, height: 44)))
        barStyle = .default
        isTranslucent = true
        self.textField = textField
        self.toolBarDelegate = toolBarDelegate
        textField.inputAccessoryView = self
    }

    func setup(leftButtons: [KeyboardToolbarButton], rightButtons: [KeyboardToolbarButton]) {
        let leftBarButtons = leftButtons.map {
            $0.createButton(target: self, action: #selector(buttonTapped))
        }
        let rightBarButtons = rightButtons.map {
            $0.createButton(target: self, action: #selector(buttonTapped(sender:)))
        }
        let spaceButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
        setItems(leftBarButtons + [spaceButton] + rightBarButtons, animated: false)
    }

    required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) }
    @objc func buttonTapped(sender: UIBarButtonItem) {
        guard let type = KeyboardToolbarButton.detectType(barButton: sender) else { return }
        toolBarDelegate?.keyboardToolbar(button: sender, type: type, isInputAccessoryViewOf: textField)
    }
}

extension UITextField {
    func addKeyboardToolBar(leftButtons: [KeyboardToolbarButton],
                            rightButtons: [KeyboardToolbarButton],
                            toolBarDelegate: KeyboardToolbarDelegate) {
        let toolbar = KeyboardToolbar(for: self, toolBarDelegate: toolBarDelegate)
        toolbar.setup(leftButtons: leftButtons, rightButtons: rightButtons)
    }
}

Usage

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        textField.addKeyboardToolBar(leftButtons:  [.back, .forwardDisabled], rightButtons:  [.done], toolBarDelegate: self)
    }
}

extension ViewController: KeyboardToolbarDelegate {
   func keyboardToolbar(button: UIBarButtonItem, type: KeyboardToolbarButton, isInputAccessoryViewOf textField: UITextField) {
        print("Tapped button type: \(type) | \(textField)")
    }
}

Full example of usage

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        createTextField(frame: CGRect(x: 50, y: 50, width: 200, height: 40), leftButtons: [.backDisabled, .forward], rightButtons: [.cancel])
        createTextField(frame: CGRect(x: 50, y: 120, width: 200, height: 40), leftButtons: [.back, .forwardDisabled], rightButtons: [.done])
    }

    private func createTextField(frame: CGRect, leftButtons: [KeyboardToolbarButton] = [], rightButtons: [KeyboardToolbarButton] = []) {
        let textField = UITextField(frame: frame)
        textField.borderStyle = .roundedRect
        view.addSubview(textField)
        textField.addKeyboardToolBar(leftButtons: leftButtons, rightButtons: rightButtons, toolBarDelegate: self)
    }
}

extension ViewController: KeyboardToolbarDelegate {
   func keyboardToolbar(button: UIBarButtonItem, type: KeyboardToolbarButton, isInputAccessoryViewOf textField: UITextField) {
        print("Tapped button type: \(type) | \(textField)")
    }
}

Xcode 11.2 layout problem

Results

enter image description here


enter image description here


enter image description here

Vasily Bodnarchuk
  • 24,482
  • 9
  • 132
  • 127
  • I fully implement your code but I'm facing with an interesting bug. As you wrote I can't tab disabled buttons but they are shown. However, I can tab enabled buttons but they are not shown. So It needs to be total opposite. Enabled buttons needs to be seen and vice-versa – Emre Önder Mar 15 '18 at 07:45
  • Sorry, I do not think, that I understand your problem. What functionality do you want to implement? – Vasily Bodnarchuk Mar 15 '18 at 07:53
  • I don't want to implement anything. I just face with a problem. Texts for disabled buttons are fully seen but I can't see texts of enabled buttons. When I tab enabled buttons, I can see texts which needs to be vice-versa. If I change texts with UIImage, everything works. – Emre Önder Mar 15 '18 at 07:54
  • This is an awesome implementation kudos! – anoop4real Jan 16 '20 at 12:43
3

Try this.

override var inputAccessoryView: UIView? {
    get {
       return containerView
    }
}
override var canBecomeFirstResponder: Bool {
   return true
}
Suraj Kumar
  • 5,547
  • 8
  • 20
  • 42
Hoang Anh Tuan
  • 370
  • 3
  • 9
1

Changes to make to your code:

  • Give accessoryView a height
  • Delete var myView: customUIView & the entire viewDidLoad() override
ma11hew28
  • 121,420
  • 116
  • 450
  • 651