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

13

55

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

Slink answered 23/5, 2010 at 20:14 Comment(0)
G
36

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.

Gallager answered 23/5, 2010 at 20:31 Comment(9)
Is there no way of doing this with public calls? Having my app get rejected kind of defeats the purpose of writing it in the first place =)Slink
@carloe: Why are you needing it in the first place? :)Gallager
I could very well be going at this the wrong way =) I have a UIView that needs to be positioned differently depending on whether or not the iPad's virtual keyboard is displayed on the screen...Slink
@carloe: You can listen to the UIKeyboard[Will|Did][Show|Hide]Notification notifications and adjust the view's position accordingly, just like everyone did on the iPhone (when the keyboard appears and disappears)...Gallager
I would like to point out that the WillShowNotification is always called when you have an accessoryView set. So if you are trying to test whether to show an accessoryView, this is not going to work properly.Sacellum
How to check for height then if you are using an accessoryView for iOS 8?Dairying
Old comments, but it's a good thing to mention that on iOS 9, UIKeyboardWillShowNotification is fired even if an external keyboard is connected. This occurs since iOS 9 introduces a toolbar on the virtual keyboard with paste/undo/redo actions that shows even when an external keyboard is connected.Boys
So how to do this for iOS9 now?Himmler
See https://mcmap.net/q/339340/-how-to-reliably-detect-if-an-external-keyboard-is-connected-on-ios-9/1873374 for discussion on how to do this for iOS9.Senzer
T
29

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.
Thereof answered 22/4, 2011 at 23:13 Comment(1)
Thats the only way to achieve it if an inputAccessoryView is associated. The point worth noting here is with iOS 8 onwards, they have changed the way origin is set on device. Everytime with any orientation the top- left corner is (0,0).Abandon
S
8

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);
Sodomy answered 26/2, 2014 at 18:8 Comment(0)
A
5

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.

Ania answered 4/3, 2012 at 1:37 Comment(0)
I
4

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;
Impanation answered 13/9, 2012 at 23:40 Comment(1)
A switch statement would be cleaner.Gormandize
R
3

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;
}
Rafflesia answered 12/7, 2013 at 20:5 Comment(0)
G
2

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!
}
Gormandize answered 11/4, 2016 at 15:54 Comment(0)
J
2

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];
}
Justis answered 26/5, 2016 at 6:23 Comment(0)
F
1

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;
Furnary answered 13/4, 2016 at 9:39 Comment(0)
C
1

This is an old thread, but as of iOS 14 we now have proper APIs for tracking hardware keyboards via GameController framework using GCKeyboard and GCKeyboardDidConnect/GCKeyboardDidDisconnect notifications.

You could do something like this:

import GameController

class ViewController: UIViewController {
    var isHardwareKeyboardConnected: Bool

    init() {
        isHardwareKeyboardConnected = GCKeyboard.coalesced != nil
        super.init(nibName: nil, bundle: nil)
        startObservingHardwareKeyboard()
    }

    func startObservingHardwareKeyboard() {
        NotificationCenter.default.addObserver(self, selector: #selector(hardwareKeyboardDidConnect), name: .GCKeyboardDidConnect, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(hardwareKeyboardDidDisconnect), name: .GCKeyboardDidDisconnect, object: nil)
    }

    @objc func hardwareKeyboardDidConnect(_ notification: Notification) {
        print("[Keyboard] Hardware keyboard did connect")
        isHardwareKeyboardConnected = true
    }

    @objc func hardwareKeyboardDidDisconnect(_ notification: Notification) {
        print("[Keyboard] Hardware keyboard did disconnect")
        isHardwareKeyboardConnected = false
    }
}
Confederation answered 18/11, 2022 at 11:31 Comment(0)
T
0

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.

Trouvaille answered 26/4, 2013 at 13:46 Comment(0)
G
0

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";

    }
Geniagenial answered 23/3, 2021 at 16:50 Comment(0)
R
-3

@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);
Ratline answered 11/12, 2014 at 7:11 Comment(1)
What about orientations?Gormandize

© 2022 - 2024 — McMap. All rights reserved.