How can I find portion of my view which isn't

2019-04-10 13:14发布

问题:

I've got a view controller showing a view with a UITextView, and I want to resize the view when the keyboard appears so that the UITextView isn't covered by the keyboard. I have this working correctly in almost all cases. I'm still seeing some weirdness on the iPad, only when the view controller is presented in ModalPresentationStyleFormSheet, and only in LandscapeRight orientation, as far as I can tell.

Relevant parts of my view controller's -keyboardWillShow:

// We'll store my frame above the keyboard in availableFrame
CGRect availableFrame = self.view.frame;

// Find the keyboard size
NSDictionary *userInfo = [notification userInfo];
NSValue keyboardFrameScreenValue = userInfo[UIKeyboardFrameBeginUserInfoKey];
CGRect keyboardFrameScreen =  [keyboardFrameScreenValue CGRectValue];
CGRect keyboardFrame = [self.view convertRect:keyboardFrameScreen fromView:nil];
CGSize keyboardSize = keyboardFrame.size;

// Figure out how much of my frame is covered by the keyboard
CGRect screenBounds = [self.view convertRect:[UIScreen mainScreen].bounds
                                    fromView:nil];
CGRect myBoundsScreen = [self.view boundsInWindow]; // See below
CGFloat myBottom = myBoundsScreen.origin.y + myBoundsScreen.size.height;
CGFloat keyboardTop = screenBounds.size.height - keyboardSize.height;
CGFloat lostHeight = myBottom - keyboardTop;
availableFrame.size.height -= lostHeight;

-[UIView boundsInWindow]:

- (CGRect)boundsInWindow {
  UIInterfaceOrientation orientation =
    [UIApplication sharedApplication].statusBarOrientation;
  CGRect bounds = [self convertRect:self.bounds toView:self.window];
  if (UIInterfaceOrientationIsLandscape(orientation)) {
    // Swap origin
    CGFloat x = bounds.origin.y;
    bounds.origin.y = bounds.origin.x;
    bounds.origin.x = x;
    // Swap size
    CGFloat width = bounds.size.height;
    bounds.size.height = bounds.size.width;
    bounds.size.width = width;
  }

  return bounds;
}

This works, most of the time. When my app is in user interface orientation LandscapeRight though, the origin I get from -boundsInWindow is quite a bit lower than it should be. What could be causing this?

Thanks for any assistance!

回答1:

Will's answer was the right idea but I had to tweak it quite a bit to get it right

extension UIView {
    func heightCoveredByKeyboardOfSize(keyboardSize: CGSize) -> CGFloat {
        let frameInWindow = convertRect(bounds, toView: nil)
        guard let windowBounds = window?.bounds else { return 0 }

        let keyboardTop = windowBounds.size.height - keyboardSize.height
        let viewBottom = frameInWindow.origin.y + frameInWindow.size.height

        return max(0, viewBottom - keyboardTop)
    }
}


回答2:

Here's how I'm handling the keyboard with iPad form sheets:

- (void)UIKeyboardDidShowNotification:(NSNotification*)aNotification
{
    NSDictionary *userInfo = [aNotification userInfo];
    CGSize keyboardSize = [userInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;

    UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, [self.view heightCoveredByKeyboardOfSize:keyboardSize], 0.0);

    [UIView animateWithDuration:.25f animations:^{
        self.scrollView.contentInset = contentInsets;
        self.scrollView.scrollIndicatorInsets = contentInsets;
    }];
}

Category on UIView:

- (CGFloat)heightCoveredByKeyboardOfSize:(CGSize)keyboardSize
{
    UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
    CGRect frameInWindow = [self convertRect:self.bounds toView:nil];
    CGRect windowBounds = self.window.bounds;

    CGFloat keyboardTop;
    CGFloat heightCoveredByKeyboard;

    //Determine height of the view covered by the keyboard relative to current rotation

    switch (orientation) {
        case UIInterfaceOrientationLandscapeLeft:
            keyboardTop = windowBounds.size.width - keyboardSize.width;
            heightCoveredByKeyboard = CGRectGetMaxX(frameInWindow) - keyboardTop;
            break;
        case UIInterfaceOrientationLandscapeRight:
            keyboardTop = windowBounds.size.width - keyboardSize.width;
            heightCoveredByKeyboard = windowBounds.size.width - frameInWindow.origin.x - keyboardTop;
            break;
        case UIInterfaceOrientationPortraitUpsideDown:
            keyboardTop = windowBounds.size.height - keyboardSize.height;
            heightCoveredByKeyboard = windowBounds.size.height - frameInWindow.origin.y - keyboardTop;
            break;
        default:
            keyboardTop = windowBounds.size.height - keyboardSize.height;
            heightCoveredByKeyboard = CGRectGetMaxY(frameInWindow) - keyboardTop;
            break;
    }

    return MAX(0.0f,heightCoveredByKeyboard);
}


回答3:

I think I've found a pretty decent answer. You can use the convertRect:fromView: method on UIView to convert coordinate systems.

In swift code, the selector that UIKeyboardNotification calls look something like this:

func keyboardWasShown(notification: NSNotification) {
    let info = notification.userInfo!
    let keyboardRect = (info[UIKeyboardFrameEndUserInfoKey]! as NSValue).CGRectValue()

    // Convert the keyboard rect into the view controller's coordinate system
    // The fromView: nil means keyboardRect is in the main window's coordinate system
    let newSize = self.view.convertRect(keyboardRect, fromView: nil)

    // And then this is the part that gets covered!
    let keyboardCoveredHeight = self.view.bounds.height - newSize.origin.y
    // Note: you may have to use self.navigationController!.view.bounds.height if
    // you've wrapped your view in a navigation controller because this will 
    // include the height of the navigation bar

    // You can then adjust the content inset of the text view accordingly
    textView.scrollIndicatorInsets.bottom = keyboardCoveredHeight
    textView.contentInset.bottom = keyboardCoveredHeight
}