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:
Fix:
After:
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+) andsafeAreaLayoutGuide
(iOS 11) properties of the view set asinputAccessoryView
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 theinputAccessoryView
, so that it leaves no useless space above the keyboard
- when the keyboard is not visible, the
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:
Wrap any UIView/UIButton/UILabel/etc using
SafeAreaInputAccessoryViewWrapperView(for:)
:SafeAreaInputAccessoryViewWrapperView(for: button)
Store a reference to this somewhere in your class:
let button = UIButton(type: .system) lazy var wrappedButton: SafeAreaInputAccessoryViewWrapperView = { return SafeAreaInputAccessoryViewWrapperView(for: button) }()
Return the reference in
inputAccessoryView
:override var inputAccessoryView: UIView? { return wrappedButton }
(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!