The following code fixes an issue in Jay's answer, which assumes that UIKeyboardWillShowNotification
will not fire again when the keyboard is already present.
When typing with the Japanese/Chinese keyboard, iOS fires an extra UIKeyboardWillShowNotification
with the new keyboard frame even though the keyboard is already present, leading to the height of the self.textView
being reduced a second time in the original code.
This reduces self.textView
to almost nothing. It then becomes impossible to recover from this problem since we will only expect a single UIKeyboardWillHideNotification
the next time the keyboard is dismissed.
Instead of subtracting/adding height to self.textView
depending on whether the keyboard is shown/hidden as in the original code, the following code just calculates the maximum possible height for self.textView
after subtracting the height of the keyboard on screen.
This assumes that self.textView
is suppose to fill the entire view of the view controller, and there's no other subview that needs to be visible.
- (void)resizeTextViewWithKeyboardNotification:(NSNotification*)notif {
NSDictionary* userInfo = [notif userInfo];
NSTimeInterval animationDuration;
UIViewAnimationCurve animationCurve;
CGRect keyboardFrameInWindowsCoordinates;
[[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:&animationCurve];
[[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&animationDuration];
[[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] getValue:&keyboardFrameInWindowsCoordinates];
[self resizeTextViewToAccommodateKeyboardFrame:keyboardFrameInWindowsCoordinates
withAnimationDuration:animationDuration
animationCurve:animationCurve];
}
- (void)resizeTextViewToAccommodateKeyboardFrame:(CGRect)keyboardFrameInWindowsCoordinates
withAnimationDuration:(NSTimeInterval)duration
animationCurve:(UIViewAnimationCurve)curve
{
CGRect fullFrame = self.view.frame;
CGRect keyboardFrameInViewCoordinates =
[self.view convertRect:keyboardFrameInWindowsCoordinates fromView:nil];
// Frame of the keyboard that intersects with the view. When keyboard is
// dismissed, the keyboard frame still has width/height, although the origin
// keeps the keyboard out of the screen.
CGRect keyboardFrameVisibleOnScreen =
CGRectIntersection(fullFrame, keyboardFrameInViewCoordinates);
// Max frame availble for text view. Assign it to the full frame first
CGRect newTextViewFrame = fullFrame;
// Deduct the the height of any keyboard that's visible on screen from
// the height of the text view
newTextViewFrame.size.height -= keyboardFrameVisibleOnScreen.size.height;
if (duration)
{
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:duration];
[UIView setAnimationCurve:curve];
}
// Adjust the size of the text view to the new one
self.textView.frame = newTextViewFrame;
if (duration)
{
[UIView commitAnimations];
}
}
Also, don't forget to register the keyboard notifications in viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
NSNotificationCenter* notifCenter = [NSNotificationCenter defaultCenter];
[notifCenter addObserver:self selector:@selector(resizeTextViewWithKeyboardNotification:) name:UIKeyboardWillShowNotification object:nil];
[notifCenter addObserver:self selector:@selector(resizeTextViewWithKeyboardNotification:) name:UIKeyboardWillHideNotification object:nil];
}
About splitting the resizing code into two parts
The reason why the textView resizing code is split into two parts (resizeTextViewWithKeyboardNotification:
and resizeViewToAccommodateKeyboardFrame:withAnimationDuration:animationCurve:
) is to fix another issue when the keyboard persists through a push from one view controller to another (see How do I detect the iOS keyboard when it stays up between controllers?).
Since the keyboard is already present before the view controller is pushed, there's no additional keyboard notifications being generated by iOS, and thus no way to resize the textView
based on those keyboard notifications.
The above code (as well as the original code) that resizes self.textView
will thus only work when the keyboard is shown after the view has been loaded.
My solution is to create a singleton that stores the last keyboard coordinates, and on - viewDidAppear:
of the viewController, call:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// Resize the view if there's any keyboard presence before this
// Only call in viewDidAppear as we are unable to convertRect properly
// before view is shown
[self resizeViewToAccommodateKeyboardFrame:[[UASKeyboard sharedKeyboard] keyboardFrame]
withAnimationDuration:0
animationCurve:0];
}
UASKeyboard
is my singleton here. Ideally we should call this in - viewWillAppear:
, however in my experience (at least on iOS 6), the convertRect:fromView:
method that we need to use in resizeViewToAccommodateKeyboardFrame:withAnimationDuration:animationCurve:
does not properly convert the keyboard frame to the view coordinates before the view is fully visible.