I have a messaging app that has the typical UI design of a text field at the bottom of a full screen table view. I am setting that text field to be the view controller\'s
Until safe are insets are guided by iOS automatically, simple workaround would be to wrap your accessory in container view and set bottom space constraint between accesory view and container view to match safe area insets of window.
Note: Of course this workaround can double your accessory view spacing from bottom when iOS update fixes bottom spacing for accessory views.
E.g.
- (void) didMoveToWindow {
[super didMoveToWindow];
if (@available(iOS 11.0, *)) {
self.bottomSpaceConstraint.constant = self.window.safeAreaInsets.bottom;
}
}
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!
After much research and trials, this seems to be a working solution for trying to use UIToolbar
with a text input's inputAccessoryView
. (Most of the existing solutions are for using fixed accessory view instead of assigning it to a text view (and hiding it when the keyboard is closed).)
The code is inspired by https://stackoverflow.com/a/46510833/2603230. Basically, we first create a custom view that has a toolbar subview:
class CustomInputAccessoryWithToolbarView: UIView {
public var toolbar: UIToolbar!
override init(frame: CGRect) {
super.init(frame: frame)
// https://stackoverflow.com/a/58524360/2603230
toolbar = UIToolbar(frame: frame)
// Below is adopted from https://stackoverflow.com/a/46510833/2603230
self.addSubview(toolbar)
self.autoresizingMask = .flexibleHeight
toolbar.translatesAutoresizingMaskIntoConstraints = false
toolbar.leadingAnchor.constraint(
equalTo: self.leadingAnchor,
constant: 0
).isActive = true
toolbar.trailingAnchor.constraint(
equalTo: self.trailingAnchor,
constant: 0
).isActive = true
toolbar.topAnchor.constraint(
equalTo: self.topAnchor,
constant: 0
).isActive = true
// This is the important part:
if #available(iOS 11.0, *) {
toolbar.bottomAnchor.constraint(
equalTo: self.safeAreaLayoutGuide.bottomAnchor,
constant: 0
).isActive = true
} else {
toolbar.bottomAnchor.constraint(
equalTo: self.layoutMarginsGuide.bottomAnchor,
constant: 0
).isActive = true
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// https://stackoverflow.com/a/46510833/2603230
// 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
}
}
Then you can set it as an inputAccessoryView
for a text input normally: (You should specify the frame size to avoid the warnings seen in UIToolbar with UIBarButtonItem LayoutConstraint issue)
let myAccessoryView = CustomInputAccessoryWithToolbarView(frame: CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: 44))
textView.inputAccessoryView = myAccessoryView
When you want to interact with the toolbar (e.g., set items on the toolbar), you can simply refer to the toolbar
variable:
myAccessoryView.toolbar.setItems(myToolbarItems, animated: true)
Demo: (with hardware keyboard / Command+K in simulator)
Before:
After:
inputAccessoryView
and safe area on iPhone Xwhen 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 :
bottomAnchor
accounts for the home button areabottomAnchor
is at the bottom of the inputAccessoryView
, so that it leaves no useless space above the keyboardWorking 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
In the case you already have a custom view loaded via nib file.
Add a convenience constructor like this:
convenience init() {
self.init(frame: .zero)
autoresizingMask = .flexibleHeight
}
and override intrinsicContentSize
:
override var intrinsicContentSize: CGSize {
return .zero
}
In the
nib
set the first bottom constraint (of views that should stay above the safe area) tosafeArea
and the second one tosuperview
with lowerpriority
so it can be satisfied on older iOS.
I just created a project on Github with support for iPhone X. It respects the new safe area layout guide. Use:
autoresizingMask = [.flexibleHeight]
Screenshot: