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

淺唱寂寞╮ 提交于 2019-11-27 18:21:58

After going back to the original question, I've found a solution that works.

It seems that when the regular virtual keyboard is displayed the keyboard frame is within the dimensions of the screen. However when a physical keyboard is connected and the keyboard toolbar is displayed, the keyboard frame is located offscreen. We can check if the keyboard frame is offscreen to determine if the keyboard toolbar is showing.

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

This code supports iOS 8 and iOS 9, inputAccessoryView, has double-protected constant to be ready for new changes in future versions of iOS and to support new devices:

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

Note, a hardware keyboard may present but not used.

I am using a variation on Sarah Elan's answer. I was having issues with her approach in certain views. I never quite got to the bottom of what caused the problem. But here is another way to determine if it is an ios9 external keyboard 'undo' bar that you have, rather than the full sized keyboard.

It is probably not very forward compatible since if they change the size of the undo bar, this brakes. But, it got the job done. I welcome criticism as there must be a better way...

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

Private API solution: (have to grab the private header file - use RuntimeViewer).

Works nicely for enterprise apps, where you don't have AppStore restrictions.

#import "UIKit/UIKeyboardImpl.h"

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

You can subscribe notification when the external device is connected:

[[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];

Or just retrieve the list of attached devices:

EAAccessoryManager* accessoryManager = [EAAccessoryManager sharedAccessoryManager];

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

If you make the toolbar irrelevant then the keyboard doesn't show up. Do this by blanking out its left and right groups (at least on iOS 12.4):

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

...and in case it helps here is a swifty way to observe:

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

You could try checking for peripherals that are advertising services using Core Bluetooth

CBCentralManager *centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil]; 
[centralManager scanForPeripheralsWithServices:nil options:nil];

And you should implement the delegate:

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

}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!