How to Add a Toolbar Above the iOS Keyboard (Swift)

About the Author:

Kellye Whitney
Our staff is always looking for the latest news, technologies and trends in the developer training space.
,

How to Add a Toolbar Above the iOS Keyboard (Swift)

Ever wonder how to get those buttons above the iOS keyboard? Here’s how!


It’s called an “input accessory view” and you set it on the related text field.
First you want to create a project (or use an existing one) with text fields. Connect the text fields’ delegate outlet to your view controller.

Now in the code you can claim to implement the delegate protocol:

class ViewController: UIViewController, UITextFieldDelegate {

Next you want to implement one function on that protocol:

textFieldShouldBeginEditing
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
    return true
}

This function is called when a text field is going to become the first responder – when it will begin editing. It returns a Bool – you could validate whatever you need to before allowing the user to input in this field.
If you return false, it doesn’t become the first responder and the keyboard doesn’t display.
We’ll just return true but we can take that opportunity to set the input accessory view.
We’re going to use a UIToolBar for our input accessory view. It would be inefficient to recreate it each time so we’ll make a property in the class for the toolbar:
var tbAccessoryView : UIToolbar?
For our toolbar we’ll have three buttons: Previous, Next and Submit. You may want something else or more but this is a good example.
So we need functions for each button to call:

@objc
func doBtnPrev() {
}
@objc
func doBtnNext() {
}
@objc
func doBtnSubmit() {
}

We’ll create selectors for each button that calls these so we need the @objc directive.
Next, in the shouldBeginEditing function we’ll first check to make sure the toolbar hasn’t already been created. If not, we’ll create it:

    if tbAccessoryView == nil {
        tbAccessoryView = UIToolbar.init(frame:
            CGRect.init(x: 0, y: 0,
            width: self.view.frame.size.width, height: 44))

Then we’ll create the buttons to put on the toolbar:

let bbiPrev = UIBarButtonItem.init(title: "Previous",
            style: .plain, target: self, action: #selector(doBtnPrev))
let bbiNext = UIBarButtonItem.init(title: "Next", style: .plain,
            target: self, action: #selector(doBtnNext))
let bbiSpacer = UIBarButtonItem(barButtonSystemItem: .flexibleSpace,
            target: nil, action: nil)
let bbiSubmit = UIBarButtonItem.init(title: "Submit", style: .plain,
            target: self, action: #selector(doBtnSubmit))

The bbiSpacer item is so that the previous and next buttons are on the far left while the submit button is on the far right. The spacer goes between them and scales (flexibleSpace).
Next we put the items on the toolbar:
tbAccessoryView?.items = [bbiPrev, bbiNext, bbiSpacer, bbiSubmit]
Just before the “return true” statement we want to set our toolbar as the input accessory view for the textField passed in. That means ANY textField that has this view controller as it’s delegate will get this input accessory view set before it becomes the first responder.

textField.inputAccessoryView = 
	tbAccessoryView


The next time a text field becomes a first responder, the toolbar will still exist and not need to be recreated.
Tapping the Previous, Next and Submit buttons will call the appropriate functions.
Depending on your requirements, these buttons may do different things. Certainly the “Submit” button will be subjective to whatever your app is.
Previous and next are pretty common and you’d want them to navigate to the previous or next text field. There’s a few approaches to this:

Tags

You can set a tag on each text field: 0, 1, 2… Then when a text field becomes active, you store that number. If the number is 0, you make the previous button disabled. If the tag is the same as the last field, you make the next button disabled. Or you handle rotating back through the text fields.
When the user taps previous or next you find the previous or next text field on the view like this:

    func findTFWithTag(tag : Int) -> UITextField? {
        var retMe : UITextField?
        self.view.subviews.forEach { (v) in
            if v.tag == tag, let tf = v as? UITextField {
                retMe = tf
            }
        }
        return retMe
    }

Then you make that the first responder by calling .becomeFirstResponder() on it.

2. Array

You could keep an array of the text fields and go to the previous or next one. But you’d have to create that array… in order which might mean tags again.

3. Outlets

You can create outlets to all the text fields and do a combination of the items above. Order them in an array or use their tags. Maybe even in a dictionary.
I’m going to do it like the first option (Tags) but roll over at the beginning and end. I’ll search for the largest tag number so I know where the form ends:

    var maxTag = 0
    func findMaxTFTag() {
        self.view.subviews.forEach { (v) in
            if v is UITextField, v.tag > maxTag {
                maxTag = v.tag
            }
        }
    }

Now we know where to roll over:

	0 + Previous = maxTag
	maxTag + Next = 0

We just need to create a curTag property and set it when shouldBeginEditing is called:

var curTag = 0
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
    curTag = textField.tag

That’s about it. We can use all of that in the Previous and Next functions:

    @objc
    func doBtnPrev() {
        // decrement or roll over
        curTag = curTag == 0 ? maxTag : curTag-1
        findTFWithTag(tag: curTag)?.becomeFirstResponder()
    }
    @objc
    func doBtnNext() {
        // increment or roll over
        curTag = curTag == maxTag ? 0 : curTag+1
        findTFWithTag(tag: curTag)?.becomeFirstResponder()
    }

Now the previous and next buttons will go through all the fields (both of them) and roll over when they get to the end!
Here’s the full ViewController.swift code:

//
// Created by Bear Cahill on 9/20/18.
// Copyright © 2018 Brainwash Inc. All rights reserved.
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
   var tbAccessoryView : UIToolbar?
   var maxTag = 0
   func findMaxTFTag() {
       self.view.subviews.forEach { (v) in
           if v is UITextField, v.tag > maxTag {
               maxTag = v.tag
           }
       }
   }
   func findTFWithTag(tag : Int) -> UITextField? {
       var retMe : UITextField?
       self.view.subviews.forEach { (v) in
           if v.tag == tag, let tf = v as? UITextField {
               retMe = tf
           }
       }
       return retMe
   }
   override func viewDidLoad() {
       super.viewDidLoad()
       // Do any additional setup after loading the view, typically from a nib.
       findMaxTFTag()
   }
   override func didReceiveMemoryWarning() {
       super.didReceiveMemoryWarning()
       // Dispose of any resources that can be recreated.
   }
   var curTag = 0
   func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
       curTag = textField.tag
       if tbAccessoryView == nil {
           tbAccessoryView = UIToolbar.init(frame:
               CGRect.init(x: 0, y: 0,
               width: self.view.frame.size.width, height: 44))
           let bbiPrev = UIBarButtonItem.init(title: "Previous",
                       style: .plain, target: self, action: #selector(doBtnPrev))
           let bbiNext = UIBarButtonItem.init(title: "Next", style: .plain,
                       target: self, action: #selector(doBtnNext))
           let bbiSpacer = UIBarButtonItem(barButtonSystemItem: .flexibleSpace,
                       target: nil, action: nil)
           let bbiSubmit = UIBarButtonItem.init(title: "Submit", style: .plain,
                       target: self, action: #selector(doBtnSubmit))
           tbAccessoryView?.items = [bbiPrev, bbiNext, bbiSpacer, bbiSubmit]
       }
       // set the tool bar as this text field's input accessory view
       textField.inputAccessoryView = tbAccessoryView
       return true
   }
   @objc
   func doBtnPrev() {
       // decrement or roll over
       curTag = curTag == 0 ? maxTag : curTag-1
       findTFWithTag(tag: curTag)?.becomeFirstResponder()
   }
   @objc
   func doBtnNext() {
       // increment or roll over
       curTag = curTag == maxTag ? 0 : curTag+1
       findTFWithTag(tag: curTag)?.becomeFirstResponder()
   }
   @objc
   func doBtnSubmit() {
   }
}