web-dev-qa-db-ja.com

iOS 11セーフエリアを拡張してキーボードを含める

IOS 11で導入された新しいセーフエリアレイアウトガイドは、バーの下にコンテンツが表示されないようにするのに効果的ですが、キーボードは含まれていません。つまり、キーボードが表示されても、コンテンツはその背後に隠れており、これが私が解決しようとしている問題です。

私のアプローチは、キーボード通知を聞いてから、additionalSafeAreaInsetsを使用して安全領域を調整することに基づいています。

これが私のコードです:

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

これは、MyControllerUIViewControllerであり、UITableViewが安全領域全体に広がるため、うまく機能します。キーボードが表示されたら、底部を押し上げて、キーボードの後ろにセルがないようにします。

問題はボトムバーにあります。下部にはツールバーがあり、安全領域に既に含まれています。したがって、追加の安全領域インセットとしてキーボードの高さ全体を設定すると、テーブルバーの下部が下部バーの高さ分だけ押し上げられます。このメソッドがうまく機能するためには、_additionalSafeAreaInsets.bottom_をキーボードの高さからボトムバーの高さを引いた値に等しくなるように設定する必要があります。

質問1:下部の現在の安全領域のギャップを取得するための最良の方法は何ですか?ツールバーのフレームを手動で取得し、その高さを使用しますか?または、セーフエリアレイアウトガイドから直接ギャップを取得することは可能ですか?

質問2:おそらく、キーボードのサイズを変更せずに下部バーのサイズを変更できるはずです。そのため、バーのフレームの変更をリッスンするメソッドも実装する必要があります。これはviewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator)で行うのが最善ですか?またはどこか?

ありがとうございました

15
Denis Balko

私にとってうまくいっているように見えるのは、view.safeAreaLayoutGuide.layoutFrameとキーボードフレーム、そしてその高さをadditionalSafeAreaInsets.bottom、キーボードフレーム全体の高さの代わりに。ビューコントローラーにツールバーはありませんが、タブバーがあり、正しく説明されています。

完全なコード:

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

IOS11より前のバージョンへのサポートが必要な場合は、Fabioの機能を使用して以下を追加できます。

if #available(iOS 11.0, *) { }

最終的解決:

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

下の安全な領域を除外することは私にとってうまくいきました:

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

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

私は別のアプローチを使用しています。ビュー階層に追加するビュー(KeyboardProxyView)があります。これをメインビューの下部に固定し、キーボードで高さを調整します。これは、keyboardProxyをキーボードビューのように扱うことができることを意味します。ただし、通常のビューを除き、制約を使用できます。

これにより、keyboardProxyに関連する他のビューを手動で制限できます。

例えば-私のツールバーはまったく制限されていませんが、inputField.bottom> = keyboardProxy.top

以下のコード(注-私は通知と自動レイアウトにHSObserverとPureLayoutを使用していますが、それらを避けたい場合は簡単にコードを書き直すことができます)

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