UIScrollView vs. UIWindow: Eternal iOS keyboard issue.
In my first year of iOS development, I've seen quite a few ways of moving UITextFields into view while the keyboard is present. Developers rejoice when they finally come to the conclusion of making the superview a delegate of a UIScrollView, and then incrementing the [contentSize height]
.
At first glance, this seems like an awesome way to move those little textfields above the mask of the active keyboard window being displayed above. However, I've done a lot of R&D on Apple-created applications and have found a common theme: These apps don't simply scroll the view while the user is editing or the textfield:didBecomeFirstResponder
, they actually animate/transform the entire app's key window to move above the keyboard fold.
Here's how I've done it:
// Set up your static constants below your @import/@include.
// These floats will handle the scroll amount, speed and keyboard heights.
static const CGFloat KEYBOARD_ANIMATION_DURATION = 0.3;
static const CGFloat MINIMUM_SCROLL_FRACTION = 0.2;
static const CGFloat MAXIMUM_SCROLL_FRACTION = 0.8;
static const CGFloat PORTRAIT_KEYBOARD_HEIGHT = 216;
static const CGFloat LANDSCAPE_KEYBOARD_HEIGHT = 162;
// Set a float ivar to manage the animatedDistance of the keyWindow.
@interface
CGFloat animatedDistance;
@end
// Begin the keyWindow scroll animation with this UITextField Delegate.
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
CGRect textFieldRect = [self.view.window convertRect:textField.bounds fromView:textField];
CGRect viewRect = [self.view.window convertRect:self.view.bounds fromView:self.view];
CGFloat midline = textFieldRect.origin.y + 0.5 * textFieldRect.size.height;
CGFloat numerator = midline - viewRect.origin.y - MINIMUM_SCROLL_FRACTION * viewRect.size.height;
CGFloat denominator = (MAXIMUM_SCROLL_FRACTION - MINIMUM_SCROLL_FRACTION) * viewRect.size.height;
CGFloat heightFraction = numerator / denominator;
if (heightFraction < 0.0) {
heightFraction = 0.0;
} else if (heightFraction > 1.0) {
heightFraction = 1.0;
}
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
if (orientation == UIInterfaceOrientationPortrait || orientation == UIInterfaceOrientationPortraitUpsideDown) {
animatedDistance = floor(PORTRAIT_KEYBOARD_HEIGHT * heightFraction);
} else {
animatedDistance = floor(LANDSCAPE_KEYBOARD_HEIGHT * heightFraction);
}
CGRect viewFrame = self.view.frame;
viewFrame.origin.y -= animatedDistance;
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:KEYBOARD_ANIMATION_DURATION];
[self.view setFrame:viewFrame];
[UIView commitAnimations];
}
// Reset the keyWindow scroll animation with this UITextField Delegate.
- (void)textFieldDidEndEditing:(UITextField *)textField
{
[textField resignFirstResponder];
CGRect viewFrame = self.view.frame;
viewFrame.origin.y += animatedDistance;
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:KEYBOARD_ANIMATION_DURATION];
[self.view setFrame:viewFrame];
[UIView commitAnimations];
}
These methods detect the keyboard window in it's current state and only executes the animation if the state is valid. I use these snippets in most of my apps—for the most part, they work fairly well. Hope this helps!
Written by Paris Xavier Pinkney
Related protips
3 Responses
I never noticed that happens.
Its possible.
Good solution.
@xeieshan, thanks! After looking through the latest iOS documentation, this may not be the solution they will continue to use post iOS7. I will post another pro tip as soon as the documentation is released publicly. :)
@xeieshan, it seems as though this method is even smoother for iOS 7 :) The animation is executed smoothly under the UINavigationController and UINavigationBar.