iPhone X, как обращаться с View Controller inputAccessoryView?
У меня есть приложение для обмена сообщениями, которое имеет типичный дизайн пользовательского интерфейса текстового поля в нижней части полноэкранного просмотра таблицы. Я устанавливаю это текстовое поле, чтобы быть контроллером представления inputAccessoryView
и звонит ViewController.becomeFirstResponder()
для того, чтобы получить поле для отображения в нижней части экрана.
Я понимаю, что Apple рекомендует этот способ создания структуры пользовательского интерфейса, и он отлично работает на "классических" устройствах, однако, когда я тестирую на симуляторе iPhone X, я замечаю, что при таком подходе текстовое поле не учитывает новые "безопасные области"., Текстовое поле отображается в самом низу экрана под индикатором главного экрана.
Я просмотрел документы HIG, но не нашел ничего полезного в отношении inputAccessoryView
на контроллере представления.
Это сложно, потому что, используя этот подход, я фактически не контролирую ни одно из ограничений напрямую, я просто устанавливаю inputAccessoryView
и позволяя контроллеру представления обрабатывать пользовательский интерфейс оттуда. Поэтому я не могу просто ограничить поле новыми безопасными районами.
Кто-нибудь нашел хорошую документацию по этому вопросу или знает альтернативный подход, который хорошо работает на iPhone X?
16 ответов
inputAccessoryView
и безопасная зона на iPhone X
когда клавиатура не видна,
inputAccessoryView
закреплен в самом низу экрана. Обойти это невозможно, и я думаю, что это намеренное поведение.layoutMarginsGuide
(iOS 9+) иsafeAreaLayoutGuide
(iOS 11) свойства представления установлены какinputAccessoryView
оба уважают безопасную зону, т.е. на iPhone X:- когда клавиатура не видна,
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
}
}
Это общая проблема с inputAccessoryViews на iPhone X. 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
В Xib найдите правильное ограничение в нижней части вашего дизайна и установите для элемента значение Safe Area
вместо Superview
:
Я только что создал быстрый CocoaPod с именем https://cocoapods.org/pods/SafeAreaInputAccessoryViewWrapperView, чтобы исправить это. Он также динамически устанавливает высоту упакованного представления, используя ограничения автоматического размещения, поэтому вам не нужно вручную устанавливать фрейм. Поддерживает iOS 9+.
Вот как это использовать:
Оберните любой UIView/UIButton/UILabel/ и т. Д., Используя
SafeAreaInputAccessoryViewWrapperView(for:)
: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() }
Удачи!
Просто добавьте одно расширение для 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
}
}
}
}
дубликат: jsqmessageviewcontroller панель инструментов ios11
После долгих исследований и испытаний кажется, что это рабочее решение для попытки использовать
UIToolbar
с вводом текста
inputAccessoryView
. (Большинство существующих решений предназначены для использования фиксированного дополнительного представления вместо того, чтобы назначать его текстовому представлению (и скрывать его при закрытой клавиатуре).)
Код вдохновлен /questions/45395594/iphone-x-kak-obraschatsya-s-view-controller-inputaccessoryview/45395708#45395708. По сути, мы сначала создаем настраиваемое представление, которое имеет подвид панели инструментов:
class CustomInputAccessoryWithToolbarView: UIView {
public var toolbar: UIToolbar!
override init(frame: CGRect) {
super.init(frame: frame)
// https://stackru.com/a/58524360/2603230
toolbar = UIToolbar(frame: frame)
// Below is adopted from https://stackru.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://stackru.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 с проблемой UIBarButtonItem LayoutConstraint)
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 в симуляторе)
Кажется, это ошибка iOS, и для этого есть проблема rdar: inputAccessoryViews должен учитывать безопасную область, вставленную с внешней клавиатуры на iPhone X
Я думаю, это должно быть исправлено в обновлении iOS, когда появится iPhone X.
Пока iOS не будет автоматически управлять вставками, простой обходной путь будет заключаться в том, чтобы обернуть ваш аксессуар в виде контейнера и установить ограничение пространства снизу между вспомогательным видом и видом контейнера, чтобы соответствовать вставкам безопасной области окна.
Примечание. Конечно, этот обходной путь может удвоить расстояние между вспомогательными видами снизу, когда обновление iOS исправит нижний интервал для дополнительных видов.
Например
- (void) didMoveToWindow {
[super didMoveToWindow];
if (@available(iOS 11.0, *)) {
self.bottomSpaceConstraint.constant = self.window.safeAreaInsets.bottom;
}
}
Из кода (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
Результат:
Я только что создал проект на Github с поддержкой iPhone X. Он уважает новое руководство по макету безопасной области. Использование:
autoresizingMask = [.flexibleHeight]
Скриншот:
Самый простой ответ (всего одна строка кода)
Просто используйте пользовательское представление, которое наследует от UIToolbar
вместо UIView
Как ваш inputAccessoryView
,
Также не забудьте установить маску автоматического изменения размера этого пользовательского представления в UIViewAutoresizingFlexibleHeight
,
Вот и все. Отблагодаришь позже.
Создайте иерархию представлений, в которой у вас естьcontainer view
иcontent view
. Представление контейнера может иметь цвет фона или фоновое представление, охватывающее все его границы, и оно размещает свое представление содержимого на основеsafeAreaInsets
. Если вы используете авторазметку, это так же просто, как установить нижнийAnchor представления контента равным его суперпредставлению.safeAreaLayoutGuide
.
В случае, если у вас уже есть пользовательский вид, загруженный через файл пера.
Добавьте вспомогательный конструктор так:
convenience init() {
self.init(frame: .zero)
autoresizingMask = .flexibleHeight
}
и переопределить intrinsicContentSize
:
override var intrinsicContentSize: CGSize {
return .zero
}
в
nib
установить первое нижнее ограничение (видов, которые должны оставаться выше безопасной области), чтобыsafeArea
а второйsuperview
с нижнимpriority
так что это может быть удовлетворено на старых iOS.
Решение, которое работает для меня без обходных путей:
я использую UIInputViewController
для обеспечения входного вспомогательного вида путем переопределения inputAccessoryViewController
собственность вместо inputAccessoryView
в "главном" виде контроллера.
UIInputViewController
"s inputView
установлен на мой пользовательский вид ввода (подкласс UIInputView
).
Что на самом деле помогло мне - это установить allowsSelfSizing
собственность моего UIInputView
в true
, Ограничения внутри входного представления используют безопасную область и устанавливаются таким образом, чтобы определять общую высоту представления (аналогично автоматическому изменению размера ячеек табличного представления).
- Для тех, кто использует lib JSQMessagesViewController -
Я предлагаю фиксированный форк на основе последней JSQ develop
ветвь коммит.
Это использует didMoveToWindow
решение (от @jki я верю?). Не идеально, но стоит попробовать, ожидая ответа Apple о inputAccessoryView
безопасное расположение направляющих вложения, или любое другое лучшее исправление.
Вы можете добавить это в ваш Podfile, заменив предыдущую строку JSQ:
pod 'JSQMessagesViewController', :git => 'https://github.com/Tulleb/JSQMessagesViewController.git', :branch => 'develop', :inhibit_warnings => true
Я просто добавить безопасную область для inputAccessoryView (флажок в Xcode). И измените нижнее ограничение пространства, равное основанию безопасной области вместо корневого представления inputAccessoryView.