I think I've come up with the solution to match the behaviour of Apple's apps.
First, in your viewWillAppear: subscribe to the keyboard notifications, so you know when the keyboard will show and hide, and the system will tell you the size of the keyboard, but dont' forget to unregister in your viewWillDisappear:.
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
Implement the methods similar to the below so that you adjust the size of your tableView to match the visible area once the keyboard shows. Here I'm tracking the state of the keyboard separately so I can choose when to set the tableView back to full height myself, since you get these notifications on every field change. Don't forget to implement keyboardWillHide: and choose somewhere appropriate to fix your tableView size.
-(void) keyboardWillShow:(NSNotification *)note
{
CGRect keyboardBounds;
[[note.userInfo valueForKey:UIKeyboardBoundsUserInfoKey] getValue: &keyboardBounds];
keyboardHeight = keyboardBounds.size.height;
if (keyboardIsShowing == NO)
{
keyboardIsShowing = YES;
CGRect frame = self.view.frame;
frame.size.height -= keyboardHeight;
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:0.3f];
self.view.frame = frame;
[UIView commitAnimations];
}
}
Now here's the scrolling bit, we work out a few sizes first, then we see where we are in the visible area, and set the rect we want to scroll to to be either the half view above or below the middle of the text field based on where it is in the view. In this case, we have an array of UITextFields and an enum that keeps track of them, so multiplying the rowHeight by the row number gives us the actual offset of the frame within this outer view.
- (void) textFieldDidBeginEditing:(UITextField *)textField
{
CGRect frame = textField.frame;
CGFloat rowHeight = self.tableView.rowHeight;
if (textField == textFields[CELL_FIELD_ONE])
{
frame.origin.y += rowHeight * CELL_FIELD_ONE;
}
else if (textField == textFields[CELL_FIELD_TWO])
{
frame.origin.y += rowHeight * CELL_FIELD_TWO;
}
else if (textField == textFields[CELL_FIELD_THREE])
{
frame.origin.y += rowHeight * CELL_FIELD_THREE;
}
else if (textField == textFields[CELL_FIELD_FOUR])
{
frame.origin.y += rowHeight * CELL_FIELD_FOUR;
}
CGFloat viewHeight = self.tableView.frame.size.height;
CGFloat halfHeight = viewHeight / 2;
CGFloat midpoint = frame.origin.y + (textField.frame.size.height / 2);
if (midpoint < halfHeight)
{
frame.origin.y = 0;
frame.size.height = midpoint;
}
else
{
frame.origin.y = midpoint;
frame.size.height = midpoint;
}
[self.tableView scrollRectToVisible:frame animated:YES];
}
This seems to work quite nicely.