ios iphone inputaccessoryview iphone-x

ios - iPhone X cómo manejar View Controller inputAccessoryView?



iphone-x (12)

Tengo una aplicación de mensajería que tiene el diseño de IU típico de un campo de texto en la parte inferior de una vista de tabla de pantalla completa. Estoy configurando ese campo de texto para que sea el inputAccessoryView del controlador de inputAccessoryView y llamo a ViewController.becomeFirstResponder() para que el campo aparezca en la parte inferior de la pantalla.

Entiendo que esta es la forma recomendada por Apple para lograr esta estructura de UI y funciona perfectamente en dispositivos "clásicos". Sin embargo, cuando pruebo en el simulador de iPhone X, observo que al utilizar este enfoque, el campo de texto no respeta las nuevas "áreas seguras". . El campo de texto se representa en la parte inferior de la pantalla debajo del indicador de la pantalla de inicio.

He mirado alrededor de los documentos HIG pero no he encontrado nada útil con respecto a inputAccessoryView en un controlador de vista.

Es difícil porque al usar este enfoque no estoy realmente en control de ninguna de las restricciones directamente, solo estoy configurando inputAccessoryView y dejando que el controlador de vista maneje la IU desde allí. Así que no puedo limitar el campo a las nuevas áreas seguras.

¿Alguien ha encontrado buena documentación sobre esto o conoce algún enfoque alternativo que funcione bien en el iPhone X?


inputAccessoryView y área segura en iPhone X

  • cuando el teclado no está visible, el inputAccessoryView se fija en la parte inferior de la pantalla. No hay forma de evitar eso y creo que este es un comportamiento intencionado.

  • Las layoutMarginsGuide (iOS 9+) y safeAreaLayoutGuide (iOS 11) de la vista configuradas como inputAccessoryView respetan el área segura, es decir, en el iPhone X:

    • cuando el teclado no está visible, la parte bottomAnchor representa el área del botón de inicio
    • cuando se muestra el teclado, la parte bottomAnchor está en la parte inferior de inputAccessoryView , de modo que no deja espacio inútil por encima del teclado

Ejemplo de trabajo:

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

Vea el resultado aquí.


- Para aquellos que están usando la librería JSQMessagesViewController -

Estoy proponiendo una bifurcación fija basada en el último compromiso de rama de develop JSQ.

Está utilizando la solución didMoveToWindow (de @jki, ¿creo?). No es lo ideal, pero vale la pena probar mientras espera la respuesta de Apple acerca de la guía de diseño de área segura de inputAccessoryView , o cualquier otra solución mejor.

Puede agregar esto a su Podfile, reemplazando la línea JSQ anterior:

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


Acabo de agregar un área segura a inputAccessoryView (casilla de verificación en Xcode). Y cambie la restricción del espacio inferior igual a la parte inferior del área segura en lugar de la parte inferior de la vista raíz de inputAccessoryView.

Constraint

Y resultado


Acabo de crear un proyecto en Github con soporte para iPhone X. Respeta la nueva guía de diseño de área segura. Utilizar:

autoresizingMask = [.flexibleHeight]

Captura de pantalla:


Acabo de crear un CocoaPod rápido llamado SafeAreaInputAccessoryViewWrapperView para solucionar este problema. También configura dinámicamente la altura de la vista ajustada utilizando restricciones de reproducción automática para que no tenga que configurar manualmente el marco. Soporta iOS 9+.

Aquí está cómo usarlo:

  1. Envuelva cualquier UIView / UIButton / UILabel / etc utilizando SafeAreaInputAccessoryViewWrapperView(for:) :

    SafeAreaInputAccessoryViewWrapperView(for: button)

  2. Almacene una referencia a esto en algún lugar de su clase:

    let button = UIButton(type: .system) lazy var wrappedButton: SafeAreaInputAccessoryViewWrapperView = { return SafeAreaInputAccessoryViewWrapperView(for: button) }()

  3. Devuelve la referencia en inputAccessoryView :

    override var inputAccessoryView: UIView? { return wrappedButton }

  4. (Opcional) Mostrar siempre el inputAccessoryView , incluso cuando el teclado está cerrado:

    override var canBecomeFirstResponder: Bool { return true } override func viewDidLoad() { super.viewDidLoad() becomeFirstResponder() }

¡Buena suerte!


Desde el código (Swift 4). Idea: monitorear el evento layoutMarginsDidChange y ajustar 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]) } }

Uso:

let autoSuggestionView = AutoSuggestionView() // ... textField.inputAccessoryView = autoSuggestionView

Resultado:


En Xib, encuentre una restricción correcta en la parte inferior de su diseño y configure el elemento en Safe Area lugar de Superview :

Antes de

Arreglar :

Despues


En el caso de que ya tenga una vista personalizada cargada a través de un archivo nib.

Agregue un constructor de conveniencia como este:

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

y anular intrinsicContentSize :

override var intrinsicContentSize: CGSize { return .zero }

En la nib establezca la primera restricción inferior (de las vistas que deben permanecer por encima del área segura) a safeArea y la segunda a superview con una priority más baja para que pueda satisfacerse en iOS más antiguo.


Este es un problema general con inputAccessoryViews en iPhone X. inputAccessoryView ignora las guías SafeAreaLayout de su ventana.

Para solucionarlo, debe agregar manualmente la restricción en su clase cuando la vista se desplace a su ventana:

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 aquí se refiere a inputAccessoryView.

Escribí sobre esto en detalle aquí: http://ahbou.org/post/165762292157/iphone-x-inputaccessoryview-fix


Hasta que las inserciones seguras sean guiadas automáticamente por iOS, la solución simple sería envolver su accesorio en la vista de contenedor y establecer la restricción de espacio inferior entre la vista de accesorio y la vista de contenedor para que coincida con las inserciones de la ventana en el área segura.

Nota: por supuesto, esta solución puede duplicar el espacio de la vista de accesorios desde la parte inferior cuando la actualización de iOS corrige el espacio inferior para las vistas de accesorios.

P.ej

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



Solo agregue una extensión para 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 } } } }

duplicar: jsqmessageviewcontroller ios11 toolbar