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
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)
}
}
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)
}
}
}
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();
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)
}
}