如何可靠地检测 iOS 9 上是否连接了外接键盘?

How to reliably detect if an external keyboard is connected on iOS 9?

在 iOS9 之前,确定是否连接外部键盘的最可靠方法是监听 UIKeyboardWillShowNotification 并使文本字段成为第一响应者,如 [=13] 中所述=].使用虚拟键盘时会触发通知,但不会在使用外接键盘时触发。

但是,此行为现在已随着 iOS9 发生变化。UIKeyboardWillShowNotification 也会在连接外部键盘时触发,因为现在显示了新的键盘工具栏。

仍然可以检测键盘高度,判断显示的是较小的工具栏还是较大的虚拟键盘。然而,这种方法并不可靠,因为键盘高度在各个测试版之间发生了变化,并且不能指望随着时间的推移保持不变。

是否有更可靠的方法可以与iOS 9一起使用?

您可以尝试使用 Core Bluetooth

检查正在宣传服务的外围设备
CBCentralManager *centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil]; 
[centralManager scanForPeripheralsWithServices:nil options:nil];

并且您应该实现委托:

- (void)centralManager:(CBCentralManager * _Nonnull)central
 didDiscoverPeripheral:(CBPeripheral * _Nonnull)peripheral
     advertisementData:(NSDictionary<NSString *,
                                id> * _Nonnull)advertisementData
                  RSSI:(NSNumber * _Nonnull)RSSI{

}

您可以在连接外部设备时订阅通知:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deviceConnected:) name:EAAccessoryDidConnectNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deviceDisconnected:) name:EAAccessoryDidDisconnectNotification object:nil];
[[EAAccessoryManager sharedAccessoryManager] registerForLocalNotifications];

或者只检索连接设备的列表:

EAAccessoryManager* accessoryManager = [EAAccessoryManager sharedAccessoryManager];

if (accessoryManager)
{
    NSArray* connectedAccessories = [accessoryManager connectedAccessories];
    NSLog(@"ConnectedAccessories = %@", connectedAccessories);
}

回到最初的问题后,我找到了一个有效的解决方案。

显示常规虚拟键盘时,键盘框架似乎在屏幕尺寸范围内。但是,当连接物理键盘并显示键盘工具栏时,键盘框架位于屏幕外。我们可以检查键盘框架是否在屏幕外,以确定是否显示键盘工具栏。

Objective-C

- (void) keyboardWillShow:(NSNotification *)notification {
    NSDictionary* userInfo = [notification userInfo];
    CGRect keyboardFrame = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
    CGRect keyboard = [self.view convertRect:keyboardFrame fromView:self.view.window];
    CGFloat height = self.view.frame.size.height;

    if ((keyboard.origin.y + keyboard.size.height) > height) {
        self.hasKeyboard = YES;
    }
}

Swift

@objc func keyboardWillShow(_ notification: NSNotification) {
    guard let userInfo = notification.userInfo else {return}
    let keyboardScreenEndFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
    let keyboard = self.view.convert(keyboardScreenEndFrame, from: self.view.window)
    let height = self.view.frame.size.height
    if (keyboard.origin.y + keyboard.size.height) > height {
        self.hasKeyboard = true
    }
}

我使用的是 Sarah Elan 答案的变体。在某些观点上,我对她的方法有疑问。我从来没有完全弄清楚导致问题的原因。但这里有另一种方法可以确定它是否是您拥有的 ios9 外部键盘 'undo' 栏,而不是全尺寸键盘。

它可能不是很向前兼容,因为如果他们改变撤消栏的大小,这就会刹车。但是,它完成了工作。我欢迎批评,因为一定有更好的方法...

//... somewhere ...
#define HARDWARE_KEYBOARD_SIZE_IOS9 55 
//

+ (BOOL) isExternalKeyboard:(NSNotification*)keyboardNotification {

  NSDictionary* info = [keyboardNotification userInfo];
  CGRect keyboardEndFrame;
  [[info valueForKey:UIKeyboardFrameEndUserInfoKey] getValue:&keyboardEndFrame];
  CGRect keyboardBeginFrame;
  [[info valueForKey:UIKeyboardFrameBeginUserInfoKey] getValue:&keyboardBeginFrame];

  CGFloat diff = keyboardEndFrame.origin.y - keyboardBeginFrame.origin.y;
  return fabs(diff) == HARDWARE_KEYBOARD_SIZE_IOS9;
}

此代码支持iOS 8 和iOS 9,inputAccessoryView,具有双重保护常量,为iOS 未来版本的新变化和支持新设备做好准备:

#define gThresholdForHardwareKeyboardToolbar 160.f // it's minimum height of the software keyboard on non-retina iPhone in landscape mode

- (bool)isHardwareKeyboardUsed:(NSNotification*)keyboardNotification {
    NSDictionary* info = [keyboardNotification userInfo];
    CGRect keyboardEndFrame;
    [[info valueForKey:UIKeyboardFrameEndUserInfoKey] getValue:&keyboardEndFrame];
    float height = [[UIScreen mainScreen] bounds].size.height - keyboardEndFrame.origin.y;
    return height < gThresholdForHardwareKeyboardToolbar;
}

请注意,硬件键盘可能存在但未使用。

私有 API 解决方案:(必须获取私有头文件 - 使用 RuntimeViewer)。

适用于没有 AppStore 限制的企业应用程序。

#import "UIKit/UIKeyboardImpl.h"

+ (BOOL)isHardwareKeyboardMode
{
   UIKeyboardImpl *kbi = [UIKeyboardImpl sharedInstance];
   BOOL externalKeyboard = kbi.inHardwareKeyboardMode;
   NSLog(@"Using external keyboard? %@", externalKeyboard?@"YES":@"NO");
   return externalKeyboard;
}

如果您使工具栏无关紧要,那么键盘就不会出现。通过清空其左右组来做到这一点(至少在 iOS 12.4):

textField.inputAssistantItem.leadingBarButtonGroups = []
textField.inputAssistantItem.trailingBarButtonGroups = []

...如果有帮助,这里有一个快速的观察方法:

// Watch for a soft keyboard to show up
let observer = NotificationCenter.default.addObserver(forName: UIWindow.keyboardWillShowNotification, object: nil, queue: nil) { notification in
    print("no external keyboard")
}

// Stop observing shortly after, since the keyboard should have shown by now
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
    NotificationCenter.default.removeObserver(observer)
}
  1. None 这里的答案对我有用。我有 iPad Air 和 iOS 13.x.

我想,通过检查键盘的高度,我能够做到这一点。而已!请注意,当连接外接键盘时,屏幕键盘的高度约为 50-60px。在此处查看工作演示:https://youtu.be/GKi-g0HOQUc

所以在你的活动中keyboardWillShow,只需获取键盘高度,看看它是否在 50-60 左右,如果是,那么我们可以假设连接了外接键盘。

@objc func keyboardWillShow(_ notification: NSNotification) {
        guard let userInfo = notification.userInfo else {return}
        let keyboardScreenEndFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
        let keyboard = self.view.convert(keyboardScreenEndFrame, from: self.view.window)

        // If with keyboard, the onscreen keyboard height is 55.
        // otherwise, the onscreen keyboard has >= 408px in height.
        // The 66 digit came from a Whosebug comment that the keyboard height is sometimes around that number.
        if keyboard.size.height <= 66 {
            hasExternalKeyboard = true
        } else {
            hasExternalKeyboard = false
        }
    }

iOS 14 SDK 终于带来 public API 为:GCKeyboard。检查外接键盘是否连接:

let isKeyboardConnected = GCKeyboard.coalesced != nil

备注:

  • import GameController
  • 您可能需要将其附在 if #available(iOS 14.0, *)

在 iOS 15 中,当存在硬件键盘并且您支持 iOS 的早期版本(不支持明确指示存在外部键盘)时,获得的键盘高度和宽度通过:

CGRect kbEndFrame = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];

两者的值为 0

因此您可以通过以下方式检测外部硬件:

if(kbEndFrame.size.height == 0)
    // External/virtual KB exists
else
    // Virtual on screen KB exists