iPhone X how to handle View Controller inputAccessoryView?

This is a general issue with inputAccessoryViews on iPhone X. The inputAccessoryView ignores the safeAreaLayoutGuides of its window.

To fix it we have to manually add the constraint in your class when the view moves to its window:

override func didMoveToWindow() {
    super.didMoveToWindow()
    if #available(iOS 11.0, *) {
        if let window = self.window {
            self.bottomAnchor.constraintLessThanOrEqualToSystemSpacingBelow(window.safeAreaLayoutGuide.bottomAnchor, multiplier: 1.0).isActive = true
        }
    }
}

PS: self here is referring to the inputAccessoryView.

I wrote about it in detail here: http://ahbou.org/post/165762292157/iphone-x-inputaccessoryview-fix


In Xib, find a right constraint at the bottom of your design, and set item to Safe Area instead of Superview:

Before: enter image description here

Fix: enter image description here

After: enter image description here


inputAccessoryView and safe area on iPhone X

  • when the keyboard is not visible, the inputAccessoryView is pinned on the very bottom of the screen. There is no way around that and I think this is intended behavior.

  • the layoutMarginsGuide (iOS 9+) and safeAreaLayoutGuide (iOS 11) properties of the view set as inputAccessoryView both respect the safe area, i.e on iPhone X :

    • when the keyboard is not visible, the bottomAnchor accounts for the home button area
    • when the keyboard is shown, the bottomAnchor is at the bottom of the inputAccessoryView, so that it leaves no useless space above the keyboard

Working example :

import UIKit

class ViewController: UIViewController {

    override var canBecomeFirstResponder: Bool { return true }

    var _inputAccessoryView: UIView!

    override var inputAccessoryView: UIView? {

        if _inputAccessoryView == nil {

            _inputAccessoryView = CustomView()
            _inputAccessoryView.backgroundColor = UIColor.groupTableViewBackground

            let textField = UITextField()
            textField.borderStyle = .roundedRect

            _inputAccessoryView.addSubview(textField)

            _inputAccessoryView.autoresizingMask = .flexibleHeight

            textField.translatesAutoresizingMaskIntoConstraints = false

            textField.leadingAnchor.constraint(
                equalTo: _inputAccessoryView.leadingAnchor,
                constant: 8
            ).isActive = true

            textField.trailingAnchor.constraint(
                equalTo: _inputAccessoryView.trailingAnchor,
                constant: -8
            ).isActive = true

            textField.topAnchor.constraint(
                equalTo: _inputAccessoryView.topAnchor,
                constant: 8
            ).isActive = true

            // this is the important part :

            textField.bottomAnchor.constraint(
                equalTo: _inputAccessoryView.layoutMarginsGuide.bottomAnchor,
                constant: -8
            ).isActive = true
        }

        return _inputAccessoryView
    }

    override func loadView() {

        let tableView = UITableView()
        tableView.keyboardDismissMode = .interactive

        view = tableView
    }
}

class CustomView: UIView {

    // this is needed so that the inputAccesoryView is properly sized from the auto layout constraints
    // actual value is not important

    override var intrinsicContentSize: CGSize {
        return CGSize.zero
    }
}

See the result here


I just created a quick CocoaPod called SafeAreaInputAccessoryViewWrapperView to fix this. It also dynamically sets the wrapped view's height using autolayout constraints so you don't have to manually set the frame. Supports iOS 9+.

Here's how to use it:

  1. Wrap any UIView/UIButton/UILabel/etc using SafeAreaInputAccessoryViewWrapperView(for:):

    SafeAreaInputAccessoryViewWrapperView(for: button)
    
  2. Store a reference to this somewhere in your class:

    let button = UIButton(type: .system)
    
    lazy var wrappedButton: SafeAreaInputAccessoryViewWrapperView = {
        return SafeAreaInputAccessoryViewWrapperView(for: button)
    }()
    
  3. Return the reference in inputAccessoryView:

    override var inputAccessoryView: UIView? {
        return wrappedButton
    }
    
  4. (Optional) Always show the inputAccessoryView, even when the keyboard is closed:

    override var canBecomeFirstResponder: Bool {
        return true
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        becomeFirstResponder()
    }
    

Good luck!