Working with Keyboards in iOS

Become a Subscriber

One of the things that flustered me early on in my iOS development was how little control us devs had over keyboards. In the early days, keyboards were a mostly untouchable component, but now we have the ability to add in custom buttons, utility bars, and even make completely custom keyboards. In this tutorial, we will show you:

  • how to adjust view heights with a keyboard appearing
  • how to show/hide a keyboard
  • add in a custom toolbar and buttons
  • how to the font attributes of selected text

Create the Controller and View

Start a new single page application in Xcode, and add a keyboardHeight property on the default ViewController.

class ViewController: UIViewController
{
  var keyboardHeight: CGFloat = 0
}

Now drag a text field object onto your View Controller in Storyboard. Link this text field to an outlet called bodyInput on your ViewController. Let’s also stub out a createBodyEditor method and by default, hide the keyboard if the outer view is touched.

class ViewController: UIViewController
{
  var keyboardHeight: CGFloat = 0
  @IBOutlet weak var bodyInput: UITextView!

  override func viewDidLoad()
  {
    super.viewDidLoad()
    createBodyEditor()
  }

  func createBodyEditor() 
  {
    // Stubbed out for now
  }

  func hideKeyboard()
  {
    view.endEditing(true)
  }

  override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent)
  {
    hideKeyboard()
  }
}

Create the Custom Keyboard Utility Bar

Now let’s build out body editor. In this method we will basically listen for keyboard height changes and update our keyboardHeight variable. We will also create a UIToolbar for our keyboard with custom buttons to bold, italicize, remove formatting, and hide the keyboard.

func createBodyEditor()
{
  // Register observers for keyboard show/hide states and update height property
  let notificationCenter = NSNotificationCenter.defaultCenter()
  let mainQueue = NSOperationQueue.mainQueue()

  notificationCenter.addObserverForName(UIKeyboardWillShowNotification, object: nil, queue: mainQueue) { notification in
    if let rectValue = notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue
    {
      self.keyboardHeight = rectValue.CGRectValue().size.height
    }
  }

  notificationCenter.addObserverForName(UIKeyboardWillHideNotification, object: nil, queue: mainQueue) { notification in
    self.keyboardHeight = 0
  }

  // Create a button bar for the number pad
  let toolbar = UIToolbar()
  toolbar.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 30)

  // Setup the buttons to be put in the system.
  let bold = UIBarButtonItem(title: "bold", image: UIImage(named: "bold"), style: .Plain, target: self, action: Selector("applyFontStyle:"))
  let italic = UIBarButtonItem(title: "italic", image: UIImage(named: "italic"), style: .Plain, target: self, action: Selector("applyFontStyle:"))
  let clear = UIBarButtonItem(title: "normal", image: UIImage(named: "clear"), style: .Plain, target: self, action: Selector("applyFontStyle:"))
  let save = UIBarButtonItem(title: "done", image: UIImage(named: "collapse"), style: .Plain, target: self, action: Selector("hideKeyboard"))

  //Put the buttons into the ToolBar and display the tool bar
  toolbar.setItems([bold, italic, clear, save], animated: false)
  bodyInput.inputAccessoryView = toolbar
}

Notice that each UIBarButtonItem is passed an action / selector to call when activated. We have to define the applyFontStyle method now, and the colon denotes it will pass the sender in as a parameter as well.

Bold, Italics, Removing Formatting Font Styles

iOS Font Attributes are pretty robust but also a bit confusing to work with. In our method below, we will received the sender. We switch out over the sender’s title attribute to decide what font to apply to the selection.

func applyFontStyle(sender: AnyObject)
{
  if let style = sender.title as String!!
  {
    let range: NSRange = bodyInput.selectedRange
    var font = UIFont.preferredFontForTextStyle(UIFontTextStyleBody)

    switch style
    {
      case "bold" :
        font = UIFont(name: "AvenirNext-Bold", size: 16)!
      case "italic" :
        font = UIFont(name: "AvenirNext-Italic", size: 16)!
      default :
        font = UIFont(name: "AvenirNext-Regular", size: 16)!
    }

    let dict = [NSFontAttributeName : font]
    bodyInput.textStorage.beginEditing()
    bodyInput.textStorage.setAttributes(dict, range: range)
    bodyInput.textStorage.endEditing()
  }
}