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

46

iOS 9 之前,检测外接键盘连接的最可靠方法是监听 UIKeyboardWillShowNotification 并将文本字段设置为第一响应者,如此问题所述。虚拟键盘时,通知会被触发,但使用外接键盘时不会触发。

然而,随着 iOS 9 的到来,这种行为已经改变了。由于现在会显示新的键盘工具栏,因此连接外接键盘时也会触发 UIKeyboardWillShowNotification

仍然可以检测键盘高度并判断显示的是较小的工具栏还是较大的虚拟键盘。但是,由于键盘高度在各个测试版之间已经发生了变化,所以这种方法不可靠,并且不能保证随时间而保持不变。

是否有更可靠的方法可用于 iOS 9?


只是一个问题。你为什么需要知道外部键盘是否连接? - agy
5
为了启用仅在用户使用外部键盘时才应启用的功能。 - Sarah Elan
这个怎么样? https://github.com/danielamitay/DAKeyboardControl它可以在iOS9上使用,但我不知道你是否可以检测到外部键盘是否连接。 - pixyzehn
我想知道是否连接了键盘,以便我可以显示键盘快捷键(不是使用命令键获得的那些)——我想允许输入1、2、3。这里不会有文本字段。 - David Dunham
11个回答

59

回到原问题后,我找到了一个可行的解决方案。

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

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

4
谢谢!为了计算iOS9上键盘工具栏的高度:CGFloat toolbarHeight = height - keyboard.origin.y - SoftDesigner
2
由于某种原因,第一次使用时这个程序可以正常工作,但是如果我关闭键盘并再次打开它,它就无法正确计算,并显示有键盘连接,而实际上并没有。 - trever
4
我在iOS 11的iPad上进行了测试,发现keyboard.origin.y + keyboard.size.height的值等于self.view.frame.size.height。然而,我发现在所有情况下keyboard.size.height都等于55。 - ɯɐɹʞ
3
我测试了iOS 12.1的新iPad Pro,发现它的工具栏高度是69,因此并不总是55。 - thacilima
1
更好的做法是:self.hasKeyboard = (keyboard.origin.y + keyboard.size.height) > height以确保在外部键盘被移除时,它会被设置回false。 - chrysb
显示剩余8条评论

29

iOS 14 的 SDK 终于为此提供了公共 API:GCKeyboard。要检查外部键盘是否已连接:

let isKeyboardConnected = GCKeyboard.coalesced != nil

注意:

  • import GameController 导入游戏控制器模块
  • 你可能需要将其包含在 if #available(iOS 14.0, *)

我可以确认这个有效,谢谢!不过,我还需要检查键盘是否“活动”。一旦folio键盘被插入到位,就会被注册,但我必须找到一种方法来判断它是否展开并正在使用。 - Daniel Saidi
我有同样的问题。折叠式键盘会导致上述情况返回 true。 - Nikolozi
我的意思是,从某种意义上说它与设备连接是正确的,但这对我们现在没有帮助,对吧? :) - Daniel Saidi
@DanielSaidi 不确定为什么? - kambala
2
对于Smart Keyboard Folio,即使键盘被折叠回去,将键盘连接到设备也会使上述返回值为true。然而,Grammarly可以在将设备插入其磁性底座时,以某种方式检测到键盘何时被激活。 - Daniel Saidi
显示剩余2条评论

6

这段代码支持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;
}

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

2

我正在使用Sarah Elan的答案的变体。在某些视图中,我对她的方法有问题。我从未完全弄清楚是什么导致了问题。但这里有另一种方法来确定您是否拥有ios9外部键盘“撤消”栏,而不是全尺寸键盘。

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

//... 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;
}

1
这是我最初走的路线,它确实很好地完成了工作。问题出现在iOS9 beta版本之间的大小从49变为55时。它似乎不再可靠了。 - Sarah Elan
你不需要硬编码键盘工具栏的大小。使用这行代码:CGFloat toolbarHeight = UIScreen.mainScreen().bounds.height - keyboardEndFrame.origin.y - SoftDesigner
SoftDesigner,这个值没有帮助。 - Dmitry
1
当设备处于竖屏或横屏模式且拼写检查开启或关闭时,键盘大小会有所不同。此外,当用户使用外部开发的软件键盘时,大小也会有所不同。 - feca

1
在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

不适用于我在iOS 16上 - zaitsman

1
私有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 14上,[UIKeyboardImpl sharedInstance] 抛出异常。 - kambala
也加入了专门针对iOS 14的答案 - kambala

0

这个解决方案适用于iOS 9 - iOS 16(仅限iPhone,不适用于最新的iPad)

class ViewController: UIViewController {
    // declare a proprty where you will store keyboard notifications
    private var cachedKeyboardNotification: Notification?
    
    // in this iexample tere is only one text fiels which can trigger the keyboard
    var textField: UItextField!

    override func viewDidLoad() {
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardNotifications), name: UIWindow.keyboardDidShowNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardNotifications), name: UIWindow.keyboardDidChangeFrameNotification, object: nil)
    }

   // store the keyboard notifioaction
   @objc func keyboardNotifications(_ notification: Notification) {
        cachedKeyboardNotification = notification
   }

    func isKeyboardExternal() -> Bool {
        
        guard let notification = cachedKeyboardNotification else {
            return false
        }
        guard let kbEndFrame = (notification.userInfo?[UIWindow.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else {
            return false
        }
        let offset = kbEndFrame.maxY - UIScreen.main.bounds.height
        let kbVisibleHeight = kbEndFrame.height - offset
        let accessoryViewHeight = textField.inputAccessoryView?.frame.height ?? 0
    
        return kbVisibleHeight - accessoryViewHeight == 0
    }

....

}

0

你可以尝试使用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{

}

我想检测是否连接了任何类型的外部键盘,而不是寻找特定的配件。 - Sarah Elan
@SarahElan 这可能会帮助你找到解决方案。 - agy
4
你能详细说明一下吗?这个回答需要更多的信息才能变得有用。 - Sarah Elan
@SarahElan 我在想,可以将这个方法与上面针对iOS 14的答案结合起来,使用GCKeyboard框架来确定是否插入了键盘。我现在需要这个功能,所以很快就会尝试一下。 - David H

0

如果您使工具栏变得无关紧要,则键盘不会显示。在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)
}

0

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

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

11
那么,如何确定连接的任何配件中是否有键盘? - Sarah Elan
我用条形码扫描器尝试了一下,但它没有出现在这个列表中。 - Joris Mans
尝试使用连接的闪电键盘,但没有反应。 - David H

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接