0

I have a UIToolbar that is positioned at the bottom of the screen. When the user taps on a UITextField in the toolbar, the keyboard appears, and I detect the keyboard size and move the toolbar up by changing its auto layout constraint constant. (It has a bottom spacing constraint to its superview.) This is working well, until the user undocks (or splits) the keyboard on iPad. At that point the toolbar is positioned incorrectly. I noticed in the Messages app the toolbar is always positioned above the keyboard even when the user undocks the keyboard, and it always perfectly sits above it while the user is repositioning the keyboard. After doing some research, it was suggested to handle this by listening for UIKeyboardWillChangeFrameNotification and examining UIKeyboardFrameEndUserInfoKey. However, that value may not exist, therefore I'm not sure what to do in that case.

My question is, is the correct approach to listen to keyboard frame change notification(s) and update the auto layout constraint, or is there some other approach to take (inputAccessoryView for the text field popped into mind)? If so, how does one detect and handle the various scenarios (hiding, appearing, updating frame, undocking, repositioning) to guarantee it's always positioned precisely above the keyboard or at the bottom if there is no keyboard (or there is a hardware keyboard in use)?

See below for how I am currently handling the keyboard appearance/disappearance/frame change in the UIKeyboardWillChangeFrameNotification handler (mix of Obj-C + pseudocode).

The problem with this is it incorrectly positions the toolbar upon undocking/splitting, repositioning while undocked, and using a hardware keyboard. Also I discovered expanding/collapsing QuickType while the keyboard is undocked does not trigger UIKeyboardWillChangeFrameNotification (does while docked).

keyboardFrameWillChange {
    CGSize keyboardBeginSize = [info[UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
    CGSize keyboardEndSize = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue].size;

    CGFloat newOffset = 0;
    CGFloat toolbarPositionOffset = self.toolbarBottomConstraint.constant;

    //get collection view's contentInset and contentOffset

    //keyboard is appearing or disappearing or device is rotating with keyboard up
    if keyboardEndSize.height == keyboardBeginSize.height {
        if toolbarPositionOffset > 0 { //if dismissing
            newOffset = keyboardEndSize.height;
            toolbarPositionOffset -= newOffset;
            //update content inset and offset
        } else { //else appearing
            newOffset = keyboardEndSize += newOffset;
            toolbarPositionOffset += newOffset;
            //update content inset and offset
        }
    }
    //keyboard height increasing (expanding QuickType)
    else if keyboardEndSize.height > keyboardBeginSize.height {
        newOffset = keyboardEndSize.height - keyboardBeginSize.height;
        toolbarPositionOffset += newOffset;
        //update content inset and offset
    }
    //else keyboard height decreasing (collapsing QuickType)
    else {
        newOffset = keyboardBeginSize.height - keyboardEndSize.height;
        toolbarPositionOffset -= newOffset;
        //update content inset and offset
    }

    self.toolbarBottomConstraint.constant = toolbarPositionOffset;
    //set new contentInset and offset

    [self.toolbar layoutIfNeeded];
}
Community
  • 1
  • 1
Jordan H
  • 52,571
  • 37
  • 201
  • 351
  • Just set `textField.inputAccessoryView=toolbar;` and comment out all your code written. – iphonic May 13 '15 at 06:01
  • @iphonic That causes a crash: `child view controller should have parent view controller but requested parent is ` – Jordan H May 13 '15 at 06:12
  • it works fine, you must be doing something like this http://stackoverflow.com/a/25882277/790842, so the crash. – iphonic May 13 '15 at 06:19
  • @iphonic I can't remove it from the superview, it needs to be visible while the keyboard is not visible as well. If I try to set it as the `inputAccessoryView` in `keyboardWillShow`, it doesn't appear at all. – Jordan H May 13 '15 at 06:37
  • Yes I understood that. In that case you need to listen to keyboard notifications. – iphonic May 13 '15 at 06:40

1 Answers1

0

I found a solution that resolves the issues I was encountering, although it does not provide the exact end result I was aiming for. Specifically, instead of attempting to move the toolbar above the keyboard upon undock/split, you can fix it to the bottom in that case, and only place it above the keyboard when it is docked. This resolved the problems I encountered with hardware keyboards as well.

Listen to UIKeyboardWillShowNotification and set the toolbar's constraint constant to be equal to the UIKeyboardFrameEndUserInfoKey value.

Listen to the UIKeyboardWillHideNotification and set the toolbar's constraint constant to be equal to 0.

Do not listen to UIKeyboardWillChangeFrameNotification or any other keyboard notifications.

By doing just that, those notifications will be sent when they keyboard appears or hides as you would expect. But magically (not very obviously I might add), UIKeyboardWillShowNotification will be called when the frame of the keyboard changes (user expands/collapses QuickType). And when the user undocks the keyboard, UIKeyboardWillShowNotification is triggered then UIKeyboardWillHideNotification is triggered immediately after.

This is tested with iOS 8.4, I could imagine this may differ by OS version.

Jordan H
  • 52,571
  • 37
  • 201
  • 351