IPhone X как обрабатывать View Controller inputAccessoryView?

У меня есть приложение для обмена сообщениями, которое имеет типичный дизайн пользовательского интерфейса текстового поля в нижней части полноэкранного представления таблицы. Я устанавливаю, что текстовое поле является контроллером представления inputAccessoryView и вызывает ViewController.becomeFirstResponder(), чтобы получить поле для отображения в нижней части экрана.

Я понимаю, что это рекомендуемый Apple способ выполнить эту структуру пользовательского интерфейса, и он отлично работает на "классических" устройствах, однако, когда я тестирую симулятор iPhone X, я замечаю, что, используя этот подход, текстовое поле не уважает новое "безопасные зоны". Текстовое поле отображается в самом низу экрана под индикатором домашнего экрана.

Я просмотрел документы HIG, но не нашел ничего полезного в отношении inputAccessoryView на контроллере представления.

Это сложно, потому что, используя этот подход, я не контролирую прямо ни одно из ограничений, я просто устанавливаю inputAccessoryView и позволяю контроллеру представления обрабатывать интерфейс оттуда. Поэтому я не могу просто ограничить поле в новых безопасных зонах.

Кто-нибудь нашел хорошую документацию по этому поводу или знает альтернативный подход, который хорошо работает на iPhone X?

введите описание изображения здесь

Ответ 1

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
    }
}

Посмотрите результат здесь

Ответ 2

Это общая проблема с 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

Ответ 3

В Xib найдите правильное ограничение в нижней части вашего дизайна и установите элемент в Safe Area вместо Superview:

До: enter image description here

Исправить: enter image description here

После: enter image description here

Ответ 4

Я только что создал быстрый CocoaPod под названием SafeAreaInputAccessoryViewWrapperView, чтобы исправить это. Он также динамически устанавливает развернутую высоту просмотра с использованием ограничений автоопределения, поэтому вам не нужно вручную устанавливать кадр. Поддерживает iOS 9 +.

Здесь, как его использовать:

  • Оберните любой UIView/UIButton/UILabel/etc, используя 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()
    }
    

Удачи!

Ответ 5

Просто добавьте одно расширение для 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

Ответ 7

До тех пор, пока безопасные вставки не будут автоматически ориентированы на iOS, простым обходным путем было бы обернуть ваш аксессуар в виде контейнера и установить ограничение нижнего пространства между окном доступа и видом контейнера для соответствия безопасным областям в окне.

Примечание. Конечно, это обходное решение может удвоить расстояние между аксессуарами снизу, когда обновление iOS фиксирует нижнее расстояние для дополнительных видов.

например.

- (void) didMoveToWindow {
    [super didMoveToWindow];
    if (@available(iOS 11.0, *)) {
        self.bottomSpaceConstraint.constant = self.window.safeAreaInsets.bottom;
    }
}

Ответ 8

Из кода (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

Результат:

введите описание изображения здесь введите описание изображения здесь

Ответ 9

В случае, если у вас уже есть пользовательское представление, загруженное через файл nib.

Добавьте конструктор удобства, например:

convenience init() {
    self.init(frame: .zero)
    autoresizingMask = .flexibleHeight
}

и переопределить intrinsicContentSize:

override var intrinsicContentSize: CGSize {
    return .zero
}

В nib установите первое нижнее ограничение (просмотров, которое должно оставаться выше безопасной области), до safeArea, а второе - superview с более низким priority, чтобы оно могло быть выполнено на более раннем iOS.

Ответ 10

Самый простой ответ (всего одна строка кода)

Просто используйте пользовательское представление, которое наследует от UIToolbar вместо UIView качестве вашего inputAccessoryView.

Также не забудьте установить эту маску UIViewAutoresizingFlexibleHeight пользовательского представления в UIViewAutoresizingF UIViewAutoresizingFlexibleHeight.

Это. Поблагодаришь меня позже.

Ответ 11

Решение, которое сработало для меня без обходных путей:

Я использую UIInputViewController для предоставления вспомогательного вида ввода путем переопределения свойства inputAccessoryViewController вместо inputAccessoryView в "основном" контроллере вида.

UIInputViewController inputView настроен на мое пользовательское представление ввода (подкласс UIInputView).

Что действительно помогло мне, так это установив свойство allowsSelfSizing моего UIInputView на true. Ограничения внутри входного представления используют безопасную область и устанавливаются таким образом, чтобы определять общую высоту представления (аналогично автоматическому изменению размера ячеек табличного представления).

Ответ 12

- для тех, кто использует JSQMessagesViewController lib -

Я предлагаю фиксированную fork, основанную на последнем завершении ветки develop JSQ.

Использует решение didMoveToWindow (из @jki, я верю?). Не идеально, но стоит попробовать, ожидая ответа Apple о приложении inputAccessoryView безопасного руководства по планировке области или любого другого лучшего исправления.

Вы можете добавить это в свой подфайл, заменив предыдущую строку JSQ:

pod 'JSQMessagesViewController', :git => 'https://github.com/Tulleb/JSQMessagesViewController.git', :branch => 'develop', :inhibit_warnings => true

Ответ 13

Я только что создал проект на Github с поддержкой iPhone X. Он уважает новое руководство по макету безопасной области. Использование:

autoresizingMask = [.flexibleHeight]

Скриншот:

screenshot

Ответ 14

Я просто добавляю безопасную область для вводаAccessoryView (флажок в Xcode). И измените ограничение нижнего пространства, равное дну безопасной области, вместо нижнего корневого входа inputAccessoryView.

Ограничение

И результат