我有一个消息传递应用程序,它在全屏幕表视图的底部具有文本字段的典型UI设计。我将该文本字段设置为视图控制器的inputAccessoryView
并调用ViewController.becomeFirstResponder()
以使字段显示在屏幕底部。
我理解这是苹果推荐的实现这种UI结构的方式,它在“经典”设备上运行得很好但是当我在iPhone X模拟器上测试时,我注意到使用这种方法,文本字段不尊重新的“安全区“。文本字段显示在主屏幕指示器下方屏幕的最底部。
我查看了HIG文档,但未发现有关视图控制器上inputAccessoryView
的任何有用信息。
这很难,因为使用这种方法我实际上并没有直接控制任何约束,我只是设置inputAccessoryView
并让视图控制器从那里处理UI。所以我不能把这个领域限制在新的安全区域。
有没有人找到关于此的良好文档或知道在iPhone X上运行良好的替代方法?
答案 0 :(得分:33)
inputAccessoryView
和iPhone X上的安全区域当键盘不可见时,inputAccessoryView
固定在屏幕的最底部。没有办法解决这个问题,我认为这是预期的行为。
设置为layoutMarginsGuide
的视图的safeAreaLayoutGuide
(iOS 9+)和inputAccessoryView
(iOS 11)属性都尊重安全区域,即在iPhone X上:< / p>
bottomAnchor
占据主页按钮区域bottomAnchor
位于inputAccessoryView
的底部,因此键盘上方没有任何无用的空间工作示例:
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
}
}
答案 1 :(得分:27)
这是iPhone X上的inputAccessoryViews的一般问题。 inputAccessoryView忽略其窗口的safeAreaLayoutGuides。
要修复它,您必须在视图移动到其窗口时手动在类中添加约束:
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在这里指的是inputAccessoryView。
我在这里详细介绍了它:http://ahbou.org/post/165762292157/iphone-x-inputaccessoryview-fix
答案 2 :(得分:4)
答案 3 :(得分:3)
我刚刚创建了一个名为SafeAreaInputAccessoryViewWrapperView的快速CocoaPod来解决这个问题。它还使用自动布局约束动态设置包装视图的高度,因此您不必手动设置框架。支持iOS 9 +。
以下是如何使用它:
使用SafeAreaInputAccessoryViewWrapperView(for:)
包装任何UIView / UIButton / UILabel / etc:
SafeAreaInputAccessoryViewWrapperView(for: button)
在班级的某处存储对此的引用:
let button = UIButton(type: .system)
lazy var wrappedButton: SafeAreaInputAccessoryViewWrapperView = {
return SafeAreaInputAccessoryViewWrapperView(for: button)
}()
在inputAccessoryView
中返回引用:
override var inputAccessoryView: UIView? {
return wrappedButton
}
(可选)即使键盘已关闭,也始终显示inputAccessoryView
:
override var canBecomeFirstResponder: Bool {
return true
}
override func viewDidLoad() {
super.viewDidLoad()
becomeFirstResponder()
}
答案 4 :(得分:3)
只需为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
}
}
}
}
答案 5 :(得分:2)
似乎它是一个iOS错误,并且有一个问题:inputAccessoryViews should respect safe area inset with external keyboard on iPhone X
我想这应该在iOS更新时修复,当iPhone X出现时。
答案 6 :(得分:2)
直到安全是插件由iOS自动引导,简单的解决方法是将您的配件包装在容器视图中,并在配件视图和容器视图之间设置底部空间约束以匹配窗口的安全区域插入。
注意:当iOS更新修复附件视图的底部间距时,这种解决方法当然会使配件视图间距从底部加倍。
E.g。
- (void) didMoveToWindow {
[super didMoveToWindow];
if (@available(iOS 11.0, *)) {
self.bottomSpaceConstraint.constant = self.window.safeAreaInsets.bottom;
}
}
答案 7 :(得分:1)
来自代码(Swift 4)。想法 - 监控layoutMarginsDidChange
事件并调整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])
}
}
用法:
let autoSuggestionView = AutoSuggestionView()
// ...
textField.inputAccessoryView = autoSuggestionView
结果:
答案 8 :(得分:1)
经过大量研究和试验,这似乎是尝试将UIToolbar
与文本输入的inputAccessoryView
结合使用的有效解决方案。 (大多数现有解决方案是使用固定的附件视图,而不是将其分配给文本视图(并在键盘关闭时隐藏它)。)
该代码受https://stackoverflow.com/a/46510833/2603230的启发。基本上,我们首先创建一个具有工具栏子视图的自定义视图:
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
}
}
然后您可以将其设置为通常用于文本输入的inputAccessoryView
:(您应指定帧大小,以避免在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
当您想与工具栏交互时(例如,在工具栏上设置项目),只需引用toolbar
变量即可:
myAccessoryView.toolbar.setItems(myToolbarItems, animated: true)
演示 :(使用硬件键盘/模拟器中的Command + K)
答案 9 :(得分:0)
如果您已经通过nib文件加载了自定义视图。
添加如下的便利构造函数:
convenience init() {
self.init(frame: .zero)
autoresizingMask = .flexibleHeight
}
并覆盖intrinsicContentSize
:
override var intrinsicContentSize: CGSize {
return .zero
}
在
nib
中设置第一个底部约束(应保持在安全区域之上的视图)至safeArea
,将第二个约束设置为superview
降低priority
,以便在较旧的iOS上满足。
答案 10 :(得分:0)
只需使用继承自UIToolbar
而不是UIView
的自定义视图作为inputAccessoryView
。
也不要忘记将自定义视图的自动调整大小蒙版设置为UIViewAutoresizingFlexibleHeight
。
就是这样。待会儿谢谢我。
答案 11 :(得分:0)
没有变通办法的对我有效的解决方案:
我正在使用UIInputViewController
通过覆盖inputAccessoryViewController
属性而不是“主”视图控制器中的inputAccessoryView
提供输入附件视图。
UIInputViewController
的{{1}}设置为我的自定义输入视图(inputView
的子类)。
实际上,对我来说,技巧是将我的UIInputView
的{{1}}属性设置为allowsSelfSizing
。输入视图内的约束使用安全区域,并以定义总视图高度的方式进行设置(类似于自动调整表格视图单元格的大小)。
答案 12 :(得分:-1)
- 对于那些使用JSQMessagesViewController lib的人 -
我提出了一个基于JSQ最新develop
分支提交的固定分支。
它正在使用didMoveToWindow
解决方案(来自@jki,我相信?)。在等待Apple关于inputAccessoryView
的安全区域布局指南附件或任何其他更好的解决方案的答案时,不太理想但值得尝试。
您可以将其添加到Podfile中,替换以前的JSQ行:
pod 'JSQMessagesViewController', :git => 'https://github.com/Tulleb/JSQMessagesViewController.git', :branch => 'develop', :inhibit_warnings => true
答案 13 :(得分:-1)
答案 14 :(得分:-3)
我只是向inputAccessoryView添加安全区域(Xcode上的复选框)。并将底部空间约束更改为等于安全区域的底部而不是inputAccessoryView根视图底部。