获取iOS键盘的高度而不显示键盘

时间:2014-11-17 20:22:38

标签: ios iphone cocoa-touch keyboard

我试图获得iOS键盘的高度。我已经完成并使用了订阅通知的方法,如下所述: https://gist.github.com/philipmcdermott/5183731

- (void)viewDidAppear:(BOOL) animated {
    [super viewDidAppear:animated];
    // Register notification when the keyboard will be show
    [[NSNotificationCenter defaultCenter] addObserver:self
        selector:@selector(keyboardWillShow:)
        name:UIKeyboardWillShowNotification
        object:nil];

    // Register notification when the keyboard will be hide
    [[NSNotificationCenter defaultCenter] addObserver:self
        selector:@selector(keyboardWillHide:)
        name:UIKeyboardWillHideNotification
        object:nil];
}

- (void)keyboardWillShow:(NSNotification *)notification {
    CGRect keyboardBounds;

    [[notification.userInfo valueForKey:UIKeyboardFrameBeginUserInfoKey] getValue:&keyboardBounds];

    // Do something with keyboard height
}

- (void)keyboardWillHide:(NSNotification *)notification {
    CGRect keyboardBounds;

    [[notification.userInfo valueForKey:UIKeyboardFrameBeginUserInfoKey] getValue:&keyboardBounds];

    // Do something with keyboard height
}

当用户实际显示键盘时,此功能正常。

我的问题:我有另一种观点,我们称它为micView,可以在键盘出现之前显示。用户可以在键入之前选择使用麦克风。我希望micView与键盘的高度相同,这就是为什么我需要键盘的高度,但是在键盘被迫出现之前我需要它。因此,在我需要读取高度值之前,未达到UIKeyboardWillShowNotification。

我的问题是:如何在没有键盘出现的情况下通过通知或其他方法获得键盘的高度。

我考虑过明确强制键盘出现在viewDidLoad中,这样我就可以将一个实例变量设置为该值,然后将其隐藏并删除两者的动画。但这真的是唯一的方法吗?

5 个答案:

答案 0 :(得分:5)

您可以使用的快速解决方案与您想要缓存键盘时使用的解决方案相同(第一次显示时,您会稍微延迟......)。该库是here。有趣的一点:

[[[[UIApplication sharedApplication] windows] lastObject] addSubview:field];
[field becomeFirstResponder];
[field resignFirstResponder];
[field removeFromSuperview];

所以基本上是显示它然后隐藏它。您可以收听通知,只是在没有真正看到它的情况下获取height奖金:你可以缓存它。 :)

答案 1 :(得分:5)

这个Swift类提供了一个交钥匙解决方案,可以管理所有必要的通知和初始化,让您只需调用类方法并返回键盘大小或高度。

来自Swift的电话:

let keyboardHeight = KeyboardService.keyboardHeight()
let keyboardSize = KeyboardService.keyboardSize()

从Objective-C调用:

 CGFloat keyboardHeight = [KeyboardService keyboardHeight];
 CGRect keyboardSize = [KeyboardService keyboardSize];

如果要将其用于初始视图布局,请在键盘出现之前希望键盘高度或大小的类的viewWillAppear方法中调用此方法。它不应该在viewDidLoad中调用,因为正确的值依赖于您已经布局的视图。然后,您可以使用KeyboardService返回的值设置autolayout约束常量,或以其他方式使用该值。例如,您可能希望获得prepareForSegue中的键盘高度,以帮助设置与通过嵌入segue填充的containerView内容相关联的值。

注意重新安全区域,键盘高度和iPhone X
键盘高度的值返回键盘的完整高度,它在iPhone X上延伸到屏幕边缘,而不仅仅是安全区域插入。因此,如果使用返回值设置自动布局约束值,则应将该约束附加到superview底边,而不是安全区域。

注意模拟器中的硬件键盘
连接硬件键盘时,此代码将提供该硬件键盘的屏幕高度,即没有高度。当然,这个状态确实需要考虑,因为这模拟了如果你有一个硬件键盘连接到实际设备会发生什么。因此,期望键盘高度的布局需要适当地响应键盘高度为零。

KeyboardService类:
像往常一样,如果从Objective-C调用,您只需要在Objective-C类中导入应用程序的Swift桥接头MyApp-Swift.h

import UIKit

class KeyboardService: NSObject {
    static var serviceSingleton = KeyboardService()
    var measuredSize: CGRect = CGRect.zero

    @objc class func keyboardHeight() -> CGFloat {
        let keyboardSize = KeyboardService.keyboardSize()
        return keyboardSize.size.height
    }

    @objc class func keyboardSize() -> CGRect {
        return serviceSingleton.measuredSize
    }

    private func observeKeyboardNotifications() {
        let center = NotificationCenter.default
        center.addObserver(self, selector: #selector(self.keyboardChange), name: .UIKeyboardDidShow, object: nil)
    }

    private func observeKeyboard() {
        let field = UITextField()
        UIApplication.shared.windows.first?.addSubview(field)
        field.becomeFirstResponder()
        field.resignFirstResponder()
        field.removeFromSuperview()
    }

    @objc private func keyboardChange(_ notification: Notification) {
        guard measuredSize == CGRect.zero, let info = notification.userInfo,
            let value = info[UIKeyboardFrameEndUserInfoKey] as? NSValue
            else { return }

        measuredSize = value.cgRectValue
    }

    override init() {
        super.init()
        observeKeyboardNotifications()
        observeKeyboard()
    }

    deinit {
        NotificationCenter.default.removeObserver(self)
    }    
}

头点头:
这里的observeKeyboard方法基于Peres在Objective-C中概述的原始方法回答了这个问题。

答案 2 :(得分:0)

好像this solution确实停止工作。

我修改了它:

  • 添加回调以了解通知何时以实际高度到达,
  • 将文本字段移动到另一个窗口以避免显示它,并且
  • 为在模拟器中使用并且软件键盘设置为现在显示的情况设置超时。

使用Swift 4:

import UIKit

public class KeyboardSize {
  private static var sharedInstance: KeyboardSize?
  private static var measuredSize: CGRect = CGRect.zero

  private var addedWindow: UIWindow
  private var textfield = UITextField()

  private var keyboardHeightKnownCallback: () -> Void = {}
  private var simulatorTimeout: Timer?

  public class func setup(_ callback: @escaping () -> Void) {
    guard measuredSize == CGRect.zero, sharedInstance == nil else {
      return
    }

    sharedInstance = KeyboardSize()
    sharedInstance?.keyboardHeightKnownCallback = callback
  }

  private init() {
    addedWindow = UIWindow(frame: UIScreen.main.bounds)
    addedWindow.rootViewController = UIViewController()
    addedWindow.addSubview(textfield)

    observeKeyboardNotifications()
    observeKeyboard()
  }

  public class func height() -> CGFloat {
    return measuredSize.height
  }

  private func observeKeyboardNotifications() {
    let center = NotificationCenter.default
    center.addObserver(self, selector: #selector(self.keyboardChange), name: UIResponder.keyboardDidShowNotification, object: nil)
  }

  private func observeKeyboard() {
    let currentWindow = UIApplication.shared.keyWindow

    addedWindow.makeKeyAndVisible()
    textfield.becomeFirstResponder()

    currentWindow?.makeKeyAndVisible()

    setupTimeoutForSimulator()
  }

  @objc private func keyboardChange(_ notification: Notification) {
    textfield.resignFirstResponder()
    textfield.removeFromSuperview()

    guard KeyboardSize.measuredSize == CGRect.zero, let info = notification.userInfo,
      let value = info[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue
      else { return }

    saveKeyboardSize(value.cgRectValue)
  }

  private func saveKeyboardSize(_ size: CGRect) {
    cancelSimulatorTimeout()

    KeyboardSize.measuredSize = size
    keyboardHeightKnownCallback()

    KeyboardSize.sharedInstance = nil
  }

  private func setupTimeoutForSimulator() {
    #if targetEnvironment(simulator)
    let timeout = 2.0
    simulatorTimeout = Timer.scheduledTimer(withTimeInterval: timeout, repeats: false, block: { (_) in
      print(" KeyboardSize")
      print(" .keyboardDidShowNotification did not arrive after \(timeout) seconds.")
      print(" Please check \"Toogle Software Keyboard\" on the simulator (or press cmd+k in the simulator) and relauch your app.")
      print(" A keyboard height of 0 will be used by default.")
      self.saveKeyboardSize(CGRect.zero)
    })
    #endif
  }

  private func cancelSimulatorTimeout() {
    simulatorTimeout?.invalidate()
  }

  deinit {
    NotificationCenter.default.removeObserver(self)
  }
}

以下列方式使用:

    let splashVC = some VC to show in the key window during the app setup (just after the didFinishLaunching with options)
    window.rootViewController = splashVC

    KeyboardSize.setup() { [unowned self] in
      let kbHeight = KeyboardSize.height() // != 0 :)
      // continue loading another things or presenting the onboarding or the auth
    }

答案 3 :(得分:0)

对于iOS 14.0,我注意到this solution停止了第10个通话,因为NotificationCenter停止广播keyboardChange通知。我无法完全弄清为什么会这样。

因此,我调整了解决方案以使KeyboardSize为单例,并添加了方法updateKeyboardHeight()如下:

    static let shared = KeyboardSize()

    /**
     Height of keyboard after the class is initialized
    */
    private(set) var keyboardHeight: CGFloat = 0.0

    private override init() {
        super.init()

        observeKeyboardNotifications()
        observeKeyboard()
    }

    func updateKeyboardHeight() {
        observeKeyboardNotifications()
        observeKeyboard()
    }

并将其用作

KeyboardSize.shared.updateKeyboardHeight()
let heightOfKeyboard = KeyboardSize.shared.keyboardHeight

答案 4 :(得分:-1)

Swift 5.1解决方案:

import UIKit

// MARK: - Properties & Lifecycle
class Keyboard {
    static private let shared = Keyboard()
    static private(set) var size = CGRect.zero
    static var isVisible: Bool = false

    static var height: CGFloat {
        return size.height
    }

    init() {
        observeKeyboardNotifications()
        triggerKeyboardNotification()
    }

    deinit {
        NotificationCenter.default.removeObserver(self)
    }
}

// MARK: - NotificationCenter
extension Keyboard {
    private func observeKeyboardNotifications() {
        NotificationCenter.default.addObserver(self, selector: #selector(self.handleKeyboardShow), name: UIResponder.keyboardDidShowNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(self.handleKeyboardHide), name: UIResponder.keyboardDidHideNotification, object: nil)
    }

    private func triggerKeyboardNotification() {
        let field = UITextField()
        UIApplication.shared.windows.first?.addSubview(field)
        field.becomeFirstResponder()
        field.resignFirstResponder()
        field.removeFromSuperview()
    }

    @objc private func handleKeyboardShow(_ notification: Notification) {
        Keyboard.isVisible = true

        if let info = notification.userInfo, let value = info[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
            Keyboard.size = value.cgRectValue
        }
    }

    @objc private func handleKeyboardHide(_ notification: Notification) {
        Keyboard.isVisible = false
    }
}