iPhone X comment gérer la vue contrôleur inputAccessoryView?

j'ai une application de messagerie qui a le design typique D'UI d'un champ de texte au bas d'une vue de table pleine écran. Je règle ce champ de texte pour qu'il soit le inputAccessoryView du contrôleur de la vue et j'appelle ViewController.becomeFirstResponder() afin d'obtenir le champ à afficher au bas de l'écran.

je comprends que C'est la façon recommandée par Apple d'accomplir cette structure D'interface utilisateur et cela fonctionne parfaitement sur les appareils "classiques" cependant quand je teste sur le simulateur iPhone X je remarque que l'utilisation de cette approche, le champ de texte ne respecte pas les nouvelles "zones de sécurité". Le champ de texte est affiché au bas de l'écran sous l'indicateur de l'écran d'accueil.

j'ai regardé autour des documents HIG mais je n'ai rien trouvé d'utile concernant le inputAccessoryView sur un contrôleur de vue.

c'est difficile parce qu'en utilisant cette approche Je ne suis pas réellement en contrôle de l'une des contraintes directement, je ne fais que mettre le inputAccessoryView et laisser le contrôleur de vue gérer L'interface utilisateur à partir de là. Donc je ne peux pas limiter le champ aux nouvelles zones de sécurité.

quelqu'un A trouvé une bonne documentation sur ce ou connaissez une autre approche qui fonctionne bien sur l'iPhone X?

enter image description here

14
demandé sur LOP_Luke 2017-09-18 18:23:54

11 réponses

inputAccessoryView et zone de sécurité sur iPhone X

  • lorsque le clavier n'est pas visible, le inputAccessoryView est épinglé sur le bas de l'écran. Il n'y a pas d'autre solution et je pense que c'est un comportement intentionnel.

  • les propriétés layoutMarginsGuide (iOS 9+) et safeAreaLayoutGuide (iOS 11) de la vue définie comme inputAccessoryView respectent toutes deux l'aire de sécurité, I. e sur iPhone X:

    • lorsque le clavier n'est pas visible, le bottomAnchor représente la zone du bouton d'accueil
    • lorsque le clavier est affiché, le bottomAnchor est au bas du inputAccessoryView , de sorte qu'il ne laisse pas d'espace inutile au-dessus du clavier

exemple pratique:

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

voir le résultat ici

22
répondu Alexandre Bintz 2017-10-01 09:03:06

il s'agit d'un problème général avec inputAccessoryViews sur iPhone X. La fenêtre inputAccessoryView ne tient pas compte des guides safearealayout de sa fenêtre.

pour le corriger, vous devez ajouter manuellement la contrainte dans votre classe lorsque la vue se déplace vers sa fenêtre:

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 se réfère ici à la vue inputAccessoryView.

j'ai écrit à ce sujet en détail ici: http://ahbou.org/post/165762292157/iphone-x-inputaccessoryview-fix

17
répondu ahbou 2017-09-28 08:44:18

dans Xib, trouver une contrainte de droite à la bas de votre conception, et régler l'article à Safe Area au lieu de Superview :

avant : enter image description here

Fix : enter image description here

après : enter image description here

4
répondu Vlad 2017-11-01 20:56:19

je viens de créer un CocoaPod rapide appelé SafeAreaInputAccessoryViewWrapperview pour corriger cela. Il règle aussi dynamiquement la hauteur de la vue enveloppée en utilisant des contraintes d'autolayout pour que vous n'ayez pas à régler manuellement le cadre. Prend en charge iOS 9+.

Voici comment l'utiliser:

  1. Wrap any uivi/UIButton/UILabel/etc using SafeAreaInputAccessoryViewWrapperView(for:) :

    SafeAreaInputAccessoryViewWrapperView(for: button)
    
  2. Stocker une référence à la présente quelque part dans votre classe:

    let button = UIButton(type: .system)
    
    lazy var wrappedButton: SafeAreaInputAccessoryViewWrapperView = {
        return SafeAreaInputAccessoryViewWrapperView(for: button)
    }()
    
  3. retournez la référence dans inputAccessoryView :

    override var inputAccessoryView: UIView? {
        return wrappedButton
    }
    
  4. (optionnel) toujours afficher le inputAccessoryView , même lorsque le clavier est fermé:

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

bonne chance!

3
répondu Jeff 2017-10-05 18:55:01

semble qu'il s'agisse D'un bug iOS, et il y a un problème rdar pour cela: inputAccessoryViews should respect safe area inset with external keyboard on iPhone X 151930920"

je suppose que cela devrait être corrigé dans la mise à jour iOS quand iPhone X viendra.

2
répondu Marat Saytakov 2017-09-22 12:16:14

Jusqu'à ce que les insets sûrs soient guidés par iOS automatiquement, la solution simple serait d'envelopper votre accessoire dans la vue de conteneur et de placer la contrainte d'espace de fond entre la vue d'accesory et la vue de conteneur pour correspondre à la zone de sécurité insets de fenêtre.

Note: Bien sûr, cette solution de contournement peut doubler l'espacement des vues accessoires à partir du bas lorsque la mise à jour iOS fixe l'espacement du bas pour les vues accessoires.

E. G.

- (void) didMoveToWindow {
    [super didMoveToWindow];
    if (@available(iOS 11.0, *)) {
        self.bottomSpaceConstraint.constant = self.window.safeAreaInsets.bottom;
    }
}
2
répondu jki 2017-09-28 09:21:52

il suffit d'ajouter une extension pour 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
            }
        }
     }
}
"151910920 double": jsqmessageviewcontroller ios11 de la barre d'outils

1
répondu ERbittuu 2018-03-13 12:37:37

-- pour ceux qui utilisent le JSQMessagesViewController lib --

je propose une fourchette fixe basée sur le dernier commit de branche develop du JSQ.

il utilise la solution didMoveToWindow (de @jki je crois?). Pas idéal, mais vaut la peine d'essayer en attendant la réponse D'Apple à propos de inputAccessoryView de l 'attachement du guide de mise en page zone de sécurité, ou toute autre meilleure solution.

vous pouvez ajouter ceci à votre Podfile, en remplaçant le ligne JSQ précédente:

pod 'JSQMessagesViewController', :git => 'https://github.com/Tulleb/JSQMessagesViewController.git', :branch => 'develop', :inhibit_warnings => true
0
répondu Tulleb 2017-11-08 09:56:13

du Code Swift 4. Idée - surveillance layoutMarginsDidChange et le réglage 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])
   }
}

Utilisation:

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

résultat:

enter image description here enter image description here

0
répondu Vlad 2017-12-01 15:59:48

dans le cas où vous avez déjà une vue personnalisée chargée via le fichier nib.

Ajouter un constructeur de commodité comme ceci:

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

et remplacer intrinsicContentSize :

override var intrinsicContentSize: CGSize {
    return .zero
}

dans le nib placer la première contrainte de fond (des vues qui devraient rester au-dessus de la zone de sécurité) à safeArea et la deuxième à superview avec plus bas priority de sorte qu'il peut être satisfait sur iOS plus anciens.

-1
répondu Jakub Truhlář 2017-12-18 21:21:29

je suis juste ajouter zone de sécurité à inputAccessoryView (case à cocher à Xcode). Et changer la contrainte d'espace de fond égale au fond de la zone de sécurité au lieu de inputAccessoryView vue de la racine de fond.

contrainte

et résultat

-2
répondu Danil Yusupov 2017-09-21 07:56:55