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

IosIpadKeyboard

Ios Problem Overview


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

Ios Solutions


Solution 1 - Ios

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 https://stackoverflow.com/questions/31991873/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 - Ios

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

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

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

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;

Solution 6 - Ios

Based upon this thread, I've assembled two static methods that I can easily call from keyboard notification methods to handle properly resizing views (usually UIScrollViews) when a keyboard appears, regardless of type (software vs hardware):

+ (void)keyboardWillShowHide:(NSNotification *)notification
                  inView:(UIView *)view
              adjustView:(UIView *)viewToAdjust
{
    // How much should we adjust the view's frame by?
    CGFloat yOffset = [SMKeyboardUtil keyboardOffsetForKeyboardNotification:notification
                                                                        inView:view];
    CGRect viewFrame = viewToAdjust.frame;
    viewFrame.size.height -= yOffset;

    // Get the animation parameters being used to show the keyboard. We'll use the same animation parameters as we
    // resize our view.
    UIViewAnimationCurve animationCurve;
    NSTimeInterval animationDuration;
    [notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] getValue:&animationCurve];
    [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] getValue:&animationDuration];

    // Resize the view's frame to subtract/add the height of the keyboard (and any inputAccessoryView)
    [UIView beginAnimations:@"animate resiz view" context:nil];
    [UIView setAnimationDuration:animationDuration];
    [UIView setAnimationCurve:animationCurve];
    [viewToAdjust setFrame:viewFrame];
    [UIView commitAnimations];

}

+ (CGFloat)keyboardOffsetForKeyboardNotification:(NSNotification *)notification
                                      inView:(UIView *)view
{
    NSAssert(notification.userInfo[UIKeyboardFrameBeginUserInfoKey], @"Invalid keyboard notification");

    // Get the frame of keyboard from the notification
    CGRect keyboardFrameBeginRaw = [notification.userInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue];
    CGRect keyboardFrameEndRaw = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];

    // Because the frame we get from the notification is raw screen coordinates, without accounting for device orientation,
    // we need to convert the frame to be relative to our view.
    CGRect keyboardFrameBegin = [view convertRect:keyboardFrameBeginRaw fromView:nil];
    CGRect keyboardFrameEnd = [view convertRect:keyboardFrameEndRaw fromView:nil];

    // We could examine the size of the frame, but this does not account for hardware keyboards. Instead,
    // we need to need the delta between the start and end positions to determine how much to modify
    // the size of our view.
    return keyboardFrameBegin.origin.y - keyboardFrameEnd.origin.y;
}

Solution 7 - Ios

As most of the methods in the previous answers have been deprecated with iOS 8 and 9 I intersect the keyboard reported frame with the current window to get the actual visible keyboard frame. Then you can just check if the height has changed.

CGRect reportedKeyboardFrameRaw = [[[notification userInfo] valueForKey: UIKeyboardFrameEndUserInfoKey] CGRectValue];

CGRect reportedKeyboardFrame = [self.view.window convertRect: reportedKeyboardFrameRaw fromWindow:nil];

CGRect visibleKeyboardFrame = CGRectIntersection(reportedKeyboardFrame, self.view.window.frame);

if (reportedKeyboardFrame.size.height != visibleKeyboardFrame.size.height)
{
    // External keyboard present!
}

Solution 8 - Ios

You can use the following which also calculates the height for keyboard/toolbar height when hardware keyboard is connected. You will need to subscribe to KeyboardWillShow notification:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];

then handle the notification like so:

- (void)keyboardWillShow:(NSNotification *)notification
{       
    // Information we want to determine from notification
    BOOL isHardwareKB = NO;
    CGFloat keyboardHeight;
 
    // Notification info
    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;

    // Determine if hardware keyboard fired this notification
    if ((keyboard.origin.y + keyboard.size.height) > height) {
        isHardwareKB = YES;
        keyboardHeight = height - keyboard.origin.y;    // toolbar height
    } else {
        isHardwareKB = NO;
        // As this value can change depending on rotation
        keyboardHeight = MIN(keyboardFrame.size.width, keyboardFrame.size.height);
    }

    // adjust view ui constraints ext ext depending on keyboard height 
    //  ....
}

You can also handle KeyboardWillHide notification. This will be fire when the firstResponder for both hardware and software keyboard.

- (void)keyboardWillShow:(NSNotification *)notification
{       
    // Information we want to determine from notification
    BOOL isHardwareKB; // this is irrelevant since it is hidden
    CGFloat keyboardHeight = 0; // height is now 0
  
    // Do any view layout logic here for keyboard height = 0 
    //  ...
}

Also don't forget to remove observer:

-(void) dealloc {
      [[NSNotificationCenter defaultCenter] removeObserver:self];
      [super dealloc];
}

Solution 9 - Ios

The following code gives you the keyboard frame for all orientations whether you're using a full screen view or the detail view of a split view.

NSDictionary* info = [aNotification userInfo];
CGRect frame = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGRect keyboardEndFrame = [self.view convertRect:frame fromView:nil]; //  The raw frame values are physical device coordinate.
CGSize keyboardSize = keyboardEndFrame.size;

The keyboard frame delivered by the notification is always in terms of hardware coordinates with the origin as the upper right corner of the screen when the iOS device in normal portrait mode with the home button at the bottom. The method -convertRect:fromView changes the coordinates from the window coordinates ( = hardware) to the local view coordinates.

I found that with a Bluetooth keyboard you get one UIKeyboardDidShowNotification the first time that there's a screen rotation but none after that. Makes it harder to distinguish the docked keyboard from the undocked/split and BT keyboards.

Solution 10 - Ios

This is not a direct answer for detecting if an external keyboard is present, but I'm doing this to detect the actual height that is needed to display the keyboard-related view(s) at the bottom of the screen.

CGRect keyboardFrame = [[[notification userInfo] objectForKey:@"UIKeyboardFrameEndUserInfoKey"] CGRectValue];
CGFloat keyboardRelatedViewsHeight = self.view.window.frame.size.height - keyboardFrame.origin.y;

Solution 11 - Ios

For anyone looking in Xamarin.iOS

           NSNotificationCenter.DefaultCenter.AddObserver(UIKeyboard.WillShowNotification, OnKeyboardNotification);

and then add the OnKeyboardNotification method,

 private void OnKeyboardNotification(NSNotification obj)
    {
            var window = UIApplication.SharedApplication.KeyWindow;
            var view = window.RootViewController.View;

            String eventName = (UIKeyboardExtensions.HardwareKeyboardConnected(obj, view)) ? "keyboard_hardware" : "keyboard_software";

    }

Solution 12 - Ios

@philosophistry's answer worked for me. The solution is less complicated on iOS 8:

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

CGFloat deviceHeight = [UIScreen mainScreen].bounds.size.height;    
CGFloat keyboardHeight = deviceHeight - keyboardRect.origin.y;

NSLog(@"actualKeyboardHeight = %f", keyboardHeight);

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestioncarloeView Question on Stackoverflow
Solution 1 - IoskennytmView Answer on Stackoverflow
Solution 2 - Iosuser721239View Answer on Stackoverflow
Solution 3 - IosT.J.View Answer on Stackoverflow
Solution 4 - Iosuser1172004View Answer on Stackoverflow
Solution 5 - IosphilipkdView Answer on Stackoverflow
Solution 6 - IosMarkView Answer on Stackoverflow
Solution 7 - IosRiveraView Answer on Stackoverflow
Solution 8 - IosVladView Answer on Stackoverflow
Solution 9 - IosPaul LinsayView Answer on Stackoverflow
Solution 10 - Iosuser1263865View Answer on Stackoverflow
Solution 11 - IosZygoteInitView Answer on Stackoverflow
Solution 12 - IosVibhor GoyalView Answer on Stackoverflow