开发者

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

开发者 https://www.devze.com 2022-12-31 03:29 出处:网络
Is there a way t开发者_运维技巧o detect if an external (bluetooth or usb) keyboard is connected to the iPad?An indirect and SDK-safe way is to make a text field a first responder. If the external keyb

Is there a way t开发者_运维技巧o detect if an external (bluetooth or usb) keyboard is connected to the iPad?


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.


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.


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


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.


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;


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


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


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


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.


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;


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

    }


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


@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);
0

精彩评论

暂无评论...
验证码 换一张
取 消