web-dev-qa-db-fra.com

Étendre la zone de sécurité iOS 11 pour inclure le clavier

Le nouveau guide de mise en page de la zone de sécurité introduit dans iOS 11 fonctionne très bien pour empêcher le contenu de s'afficher sous les barres, mais il exclut le clavier. Cela signifie que lorsqu'un clavier est affiché, le contenu est toujours caché derrière et c'est le problème que j'essaie de résoudre.

Mon approche est basée sur l'écoute des notifications du clavier, puis sur l'ajustement de la zone de sécurité via additionalSafeAreaInsets.

Voici mon code:

override func viewDidLoad() {
    let notificationCenter = NotificationCenter.default
    notificationCenter.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
    notificationCenter.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
    notificationCenter.addObserver(self, selector: #selector(keyboardWillChange(notification:)), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
}

//MARK: - Keyboard
extension MyViewController {
    @objc func keyboardWillShow(notification: NSNotification) {
        let userInfo = notification.userInfo!
        let keyboardHeight = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue.height

        additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: keyboardHeight, right: 0)
        UIView.animate(withDuration: 0.3) {
            self.view.layoutIfNeeded();
        }
    }

    @objc func keyboardWillHide(notification: NSNotification) {
        additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
        UIView.animate(withDuration: 0.3) {
            self.view.layoutIfNeeded();
        }
    }

    @objc func keyboardWillChange(notification: NSNotification) {
        let userInfo = notification.userInfo!
        let keyboardHeight = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue.height

        additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: keyboardHeight, right: 0)

        UIView.animate(withDuration: 0.3) {
            self.view.layoutIfNeeded();
        }
    }
}

Cela fonctionne bien car le MyController est un UIViewController avec un UITableView qui s'étend à travers toute la zone de sécurité. Maintenant, lorsque le clavier apparaît, le bas est poussé vers le haut afin qu'aucune cellule ne se trouve derrière le clavier.

Le problème est avec les barres inférieures. J'ai également une barre d'outils en bas qui est déjà incluse dans la zone de sécurité. Par conséquent, la définition de la hauteur totale du clavier comme encart de zone de sécurité supplémentaire pousse le bas de la vue de la table trop haut exactement de la hauteur de la barre inférieure. Pour que cette méthode fonctionne bien, je dois définir le additionalSafeAreaInsets.bottom Pour qu'il soit égal à la hauteur du clavier moins la hauteur de la barre inférieure.

Question 1: Quelle est la meilleure façon d'obtenir l'écart de zone de sécurité actuel en bas? Obtenir manuellement le cadre de la barre d'outils et utiliser sa hauteur? Ou est-il possible d'obtenir l'écart directement à partir du guide d'aménagement de la zone de sécurité?

Question 2: Vraisemblablement, il devrait être possible pour la barre inférieure de changer de taille sans que le clavier change de taille, donc je devrais également implémenter une méthode d'écoute pour changer le cadre de la barre. Est-ce mieux fait dans viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator)? Ou ailleurs?

Je vous remercie

15
Denis Balko

Ce qui semble fonctionner pour moi, c'est de calculer l'intersection entre view.safeAreaLayoutGuide.layoutFrame et le cadre du clavier, puis en définissant la hauteur comme additionalSafeAreaInsets.bottom, au lieu de toute la hauteur du cadre du clavier. Je n'ai pas de barre d'outils dans mon contrôleur de vue, mais j'ai une barre d'onglets et elle est correctement prise en compte.

Code complet:

import UIKit

public extension UIViewController 
{
    func startAvoidingKeyboard() 
    {    
        NotificationCenter.default
            .addObserver(self,
                         selector: #selector(onKeyboardFrameWillChangeNotificationReceived(_:)),
                         name: UIResponder.keyboardWillChangeFrameNotification,
                         object: nil)
    }

    func stopAvoidingKeyboard() 
    {
        NotificationCenter.default
            .removeObserver(self,
                            name: UIResponder.keyboardWillChangeFrameNotification,
                            object: nil)
    }

    @objc
    private func onKeyboardFrameWillChangeNotificationReceived(_ notification: Notification) 
    {
        guard 
            let userInfo = notification.userInfo,
            let keyboardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue 
        else {
            return
        }

        let keyboardFrameInView = view.convert(keyboardFrame, from: nil)
        let safeAreaFrame = view.safeAreaLayoutGuide.layoutFrame.insetBy(dx: 0, dy: -additionalSafeAreaInsets.bottom)
        let intersection = safeAreaFrame.intersection(keyboardFrameInView)

        let keyboardAnimationDuration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey]
        let animationDuration: TimeInterval = (keyboardAnimationDuration as? NSNumber)?.doubleValue ?? 0
        let animationCurveRawNSN = notification.userInfo?[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber
        let animationCurveRaw = animationCurveRawNSN?.uintValue ?? UIView.AnimationOptions.curveEaseInOut.rawValue
        let animationCurve = UIView.AnimationOptions(rawValue: animationCurveRaw)

        UIView.animate(withDuration: animationDuration,
                       delay: 0,
                       options: animationCurve,
                       animations: {
            self.additionalSafeAreaInsets.bottom = intersection.height
            self.view.layoutIfNeeded()
        }, completion: nil)
    }
}
18

Si vous avez besoin de soutien pour revenir aux versions antérieures à IOS11, vous pouvez utiliser la fonction de Fabio et ajouter:

if #available(iOS 11.0, *) { }

solution finale:

extension UIViewController {

    func startAvoidingKeyboard() {
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(_onKeyboardFrameWillChangeNotificationReceived(_:)),
                                               name: NSNotification.Name.UIKeyboardWillChangeFrame,
                                               object: nil)
    }

    func stopAvoidingKeyboard() {
        NotificationCenter.default.removeObserver(self,
                                                  name: NSNotification.Name.UIKeyboardWillChangeFrame,
                                                  object: nil)
    }

    @objc private func _onKeyboardFrameWillChangeNotificationReceived(_ notification: Notification) {
        if #available(iOS 11.0, *) {

            guard let userInfo = notification.userInfo,
                let keyboardFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else {
                    return
            }

            let keyboardFrameInView = view.convert(keyboardFrame, from: nil)
            let safeAreaFrame = view.safeAreaLayoutGuide.layoutFrame.insetBy(dx: 0, dy: -additionalSafeAreaInsets.bottom)
            let intersection = safeAreaFrame.intersection(keyboardFrameInView)

            let animationDuration: TimeInterval = (notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0
            let animationCurveRawNSN = notification.userInfo?[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber
            let animationCurveRaw = animationCurveRawNSN?.uintValue ?? UIViewAnimationOptions.curveEaseInOut.rawValue
            let animationCurve = UIViewAnimationOptions(rawValue: animationCurveRaw)

            UIView.animate(withDuration: animationDuration, delay: 0, options: animationCurve, animations: {
                self.additionalSafeAreaInsets.bottom = intersection.height
                self.view.layoutIfNeeded()
            }, completion: nil)
        }
    }
}
5
Skyborg

Exclure la zone de sécurité inférieure a fonctionné pour moi:

 NSValue keyboardBounds = (NSValue)notification.UserInfo.ObjectForKey(UIKeyboard.FrameEndUserInfoKey);

_bottomViewBottomConstraint.Constant = keyboardBounds.RectangleFValue.Height - UIApplication.SharedApplication.KeyWindow.SafeAreaInsets.Bottom;
                        View.LayoutIfNeeded();
0
Nishitha

J'utilise une approche différente. J'ai une vue (KeyboardProxyView) que j'ajoute à ma hiérarchie de vues. Je l'épingle au bas de la vue principale et ajuste sa hauteur avec le clavier. Cela signifie que nous pouvons traiter le keyboardProxy comme s'il s'agissait de la vue du clavier - sauf qu'il s'agit d'une vue normale, vous pouvez donc utiliser des contraintes dessus.

Cela me permet de contraindre mes autres vues par rapport à keyboardProxy manuellement.

par exemple. - ma barre d'outils n'est pas du tout contrainte, mais je pourrais avoir inputField.bottom> = keyboardProxy.top

Code ci-dessous (remarque - j'utilise HSObserver et PureLayout pour les notifications et la mise en page automatique - mais vous pouvez facilement réécrire ce code si vous préférez les éviter)

import Foundation
import UIKit
import PureLayout
import HSObserver

/// Keyboard Proxy view will mimic the height of the keyboard
/// You can then constrain other views to move up when the KeyboardProxy expands using AutoLayout
class KeyboardProxyView: UIView, HSHasObservers {
    weak var keyboardProxyHeight: NSLayoutConstraint!

    override func didMoveToSuperview() {
        keyboardProxyHeight = self.autoSetDimension(.height, toSize: 0)

        let names = [UIResponder.keyboardWillShowNotification,UIResponder.keyboardWillHideNotification]
        HSObserver.init(forNames: names) { [weak self](notif) in
            self?.updateKeyboardProxy(notification: notif)
        }.add(to: self)

        activateObservers()
    }

    var parentViewController: UIViewController? {
        var parentResponder: UIResponder? = self
        while parentResponder != nil {
            parentResponder = parentResponder!.next
            if let viewController = parentResponder as? UIViewController {
                return viewController
            }
        }
        return nil
    }

    func updateKeyboardProxy(notification:Notification){
        let userInfo = notification.userInfo!

        let animationDuration = (userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
        let keyboardEndFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
        let convertedKeyboardEndFrame = self.superview!.convert(keyboardEndFrame, from: self.window)
        let rawAnimationCurve = (notification.userInfo![UIResponder.keyboardAnimationCurveUserInfoKey] as! NSNumber).uint32Value << 16
        let animationCurve = UIView.AnimationOptions(rawValue:UInt(rawAnimationCurve))

        keyboardProxyHeight.constant = self.superview!.bounds.maxY - convertedKeyboardEndFrame.minY
        //keyboardProxyHeight.constant = keyboardEndFrame.height

        UIView.animate(withDuration: animationDuration, delay: 0.0, options: [.beginFromCurrentState, animationCurve], animations: {
            self.parentViewController?.view.layoutIfNeeded()
        }, completion: nil)
    }
}
0
Confused Vorlon