How can I detect if an external keyboard is present on an iPad?

Is there a way to detect if an external (bluetooth or usb) keyboard is connected to the iPad?


Solution 1:

An indirect and SDK-safe way is to make a text field a first responder. If the external keyboard is present, the UIKeyboardWillShowNotification local notification shall not be posted.

Update: This is no longer true since iOS 9, however you may use the keyboard dimensions to determine if a hardware or software keyboard is involved. See How to reliably detect if an external keyboard is connected on iOS 9? for details.

You can listen to the "GSEventHardwareKeyboardAttached" (kGSEventHardwareKeyboardAvailabilityChangedNotification) Darwin notification, but this is a private API, so it's possible your app will get rejected if you use this. To check if the external hardware is present, use the private GSEventIsHardwareKeyboardAttached() function.

UIKit listens to this and sets the UIKeyboardImpl.isInHardwareKeyboardMode property accordingly, but again this is private API.

Solution 2:

There is another level to this.

  • If you don't have an inputAccessoryView, you won't get the notification as the above explanations point out.
  • However, if you have set up an inputAccessoryView for the text view, then you will still receive a UIKeyboard notification when the external kbd is present -- the logic being that you will need to animate your view into the right location so you need the animation information contained in the notification.

Fortunately, there is enough information in the event to figure out whether the kbd will be presented, though it's still a little involved.

If we examine the notification dictionary we see this information:

UIKeyboardFrameBeginUserInfoKey = NSRect: {{0, 1024}, {768, 308}}
UIKeyboardFrameEndUserInfoKey = NSRect: {{0, 980}, {768, 308}}

That was in Portrait; if we rotate the device to PortraitUpsideDown we get:

UIKeyboardFrameBeginUserInfoKey = NSRect: {{0, -308}, {768, 308}}
UIKeyboardFrameEndUserInfoKey = NSRect: {{0, -264}, {768, 308}}

Similarly in LandscapeLeft and LandscapeRight we get different start and end locations.

Hmm... what do these numbers mean? You can see that the kbd is offscreen to start, but it does move a little. To make things worse, depending on the device orientation, the kbd locations are different.

However, we do have enough information to figure out what's going on:

  1. The kbd moves from just offscreen at the physical bottom of the device to the same height as the inputAccessoryView (but obscured by it)
  2. So in the Portrait case it moves from 1024 to 980 -- we must have an inputAccessoryView with a height of 44, which is indeed the case.
  3. So in Portrait if the end y + the inputAccessoryView height == screen height, then the kbd is not visible. You need to handle the other rotations, but that's the idea.

Solution 3:

Building on @user721239 the if condition determines if the bottom of the keyboard is out of the the frame of self.view. "convertRect" normalizes the frame for any orientation.

- (void)keyboardWillShow:(NSNotification *)notification {
keyboardFrame = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
keyboardFrame = [self.view convertRect:keyboardFrame fromView:nil]; // convert orientation
keyboardSize = keyboardFrame.size;
//NSLog(@"keyboardFrame.origin.y = %f", keyboardFrame.origin.y);
//NSLog(@"keyboardFrame.size.height = %f", keyboardFrame.size.height);
BOOL hardwareKeyboardPresent = FALSE;;
if ((keyboardFrame.origin.y + keyboardFrame.size.height) > (self.view.frame.size.height+self.navigationController.navigationBar.frame.size.height)) {
    hardwareKeyboardPresent = TRUE;
}
//NSLog(@"bottomOfKeyboard = %f", bottomOfKeyboard);
//NSLog(@"self.view.frame.size.height = %f", self.view.frame.size.height);

Solution 4:

Even using an inputAccessoryView on your UITextView instance set to an instance of a UIView with frame CGRectZero works to get delivery of the keyboard notifications working with a hardware keyboard.

Solution 5:

This is the code I use to get the height from the keyboard userInfo in UIKeyboardWillShowNotification. Works if with both physical and virtual keyboards.

NSValue* aValue = [userInfo objectForKey:UIKeyboardFrameEndUserInfoKey];

CGRect keyboardRect = [aValue CGRectValue];

CGFloat deviceHeight = [UIScreen mainScreen].bounds.size.height;
CGFloat deviceWidth = [UIScreen mainScreen].bounds.size.width;

CGFloat newKeyboardHeight;

if (interfaceOrientation == UIInterfaceOrientationPortrait)
    newKeyboardHeight = deviceHeight - keyboardRect.origin.y;
else if (interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)
    newKeyboardHeight = keyboardRect.size.height + keyboardRect.origin.y;
else if (interfaceOrientation == UIInterfaceOrientationLandscapeLeft)
    newKeyboardHeight = deviceWidth - keyboardRect.origin.x;
else
    newKeyboardHeight = keyboardRect.size.width + keyboardRect.origin.x;