可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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 inputAccessoryView
and calling ViewController.becomeFirstResponder()
in order to get the field to show at the bottom of the screen.
I understand this is the Apple recommended way of accomplishing this UI structure and it works perfectly on "classic" devices however when I test on the iPhone X simulator I notice that using this approach, the text field does not respect the new "safe areas". The text field is rendered at the very bottom of the screen underneath the home screen indicator.
I have looked around the the HIG documents but haven't found anything useful regarding the inputAccessoryView
on a view controller.
It's difficult because using this approach I'm not actually in control of any of the constraints directly, I'm just setting the inputAccessoryView
and letting the view controller handle the UI from there. So I can't just constrain the field to the new safe areas.
Has anyone found good documentation on this or know of an alternate approach that works well on the iPhone X?
回答1:
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
回答2:
This is a general issue with inputAccessoryViews on iPhone X.
The inputAccessoryView ignores the safeAreaLayoutGuides of its window.
To fix it you ou 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
回答3:
In Xib, find a right constraint at the bottom of your design, and set item to Safe Area
instead of Superview
:
Before:
Fix:
After:
回答4:
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!
回答5:
Seems it's an iOS bug, and there is a rdar issue for it: inputAccessoryViews should respect safe area inset with external keyboard on iPhone X
I guess this should be fixed in iOS update when iPhone X will come up.
回答6:
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;
}
}
回答7:
Just add one extension for JSQMessagesInputToolbar
extension JSQMessagesInputToolbar {
override open func didMoveToWindow() {
super.didMoveToWindow()
if #available(iOS 11.0, *) {
if self.window?.safeAreaLayoutGuide != nil {
self.bottomAnchor.constraintLessThanOrEqualToSystemSpacingBelow((self.window?.safeAreaLayoutGuide.bottomAnchor)!,
multiplier: 1.0).isActive = true
}
}
}
}
duplicate : jsqmessageviewcontroller ios11 toolbar
回答8:
From code (Swift 4). Idea - monitoring layoutMarginsDidChange
event and adjusting intrinsicContentSize
.
public final class AutoSuggestionView: UIView {
private lazy var tableView = UITableView(frame: CGRect(), style: .plain)
private var bottomConstraint: NSLayoutConstraint?
var streetSuggestions = [String]() {
didSet {
if streetSuggestions != oldValue {
updateUI()
}
}
}
var handleSelected: ((String) -> Void)?
public override func initializeView() {
addSubview(tableView)
setupUI()
setupLayout()
// ...
updateUI()
}
public override var intrinsicContentSize: CGSize {
let size = super.intrinsicContentSize
let numRowsToShow = 3
let suggestionsHeight = tableView.rowHeight * CGFloat(min(numRowsToShow, tableView.numberOfRows(inSection: 0)))
//! Explicitly used constraint instead of layoutMargins
return CGSize(width: size.width,
height: suggestionsHeight + (bottomConstraint?.constant ?? 0))
}
public override func layoutMarginsDidChange() {
super.layoutMarginsDidChange()
bottomConstraint?.constant = layoutMargins.bottom
invalidateIntrinsicContentSize()
}
}
extension AutoSuggestionView {
private func updateUI() {
backgroundColor = streetSuggestions.isEmpty ? .clear : .white
invalidateIntrinsicContentSize()
tableView.reloadData()
}
private func setupLayout() {
let constraint0 = trailingAnchor.constraint(equalTo: tableView.trailingAnchor)
let constraint1 = tableView.leadingAnchor.constraint(equalTo: leadingAnchor)
let constraint2 = tableView.topAnchor.constraint(equalTo: topAnchor)
//! Used bottomAnchor instead of layoutMarginGuide.bottomAnchor
let constraint3 = bottomAnchor.constraint(equalTo: tableView.bottomAnchor)
bottomConstraint = constraint3
NSLayoutConstraint.activate([constraint0, constraint1, constraint2, constraint3])
}
}
Usage:
let autoSuggestionView = AutoSuggestionView()
// ...
textField.inputAccessoryView = autoSuggestionView
Result:
回答9:
-- For those who are using the JSQMessagesViewController lib --
I am proposing a fixed fork based on the JSQ latest develop
branch commit.
It is using the didMoveToWindow
solution (from @jki I believe?). Not ideal but worth to try while waiting for Apple's answer about inputAccessoryView
's safe area layout guide attachment, or any other better fix.
You can add this to your Podfile, replacing the previous JSQ line:
pod 'JSQMessagesViewController', :git => 'https://github.com/Tulleb/JSQMessagesViewController.git', :branch => 'develop', :inhibit_warnings => true
回答10:
I just created a project on Github with support for iPhone X. It respects the new safe area layout guide. Use:
autoresizingMask = [.flexibleHeight]
Screenshot:
回答11:
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) to safeArea
and the second one to superview
with lower priority
so it can be satisfied on older iOS.
回答12:
I'm just add safe area to inputAccessoryView (checkbox at Xcode). And change bottom space constraint equal to bottom of safe area instead of inputAccessoryView root view bottom.
Constraint
And result