OK, I'll answer my own question with my solution. Of course I seem to come across the solution right after I post to SO.
My maths were all wrong.
Here is my updated version in case it helps anyone else.
This will work both landscape and portrait.
- (void)keyboardWillShow:(NSNotification *)aNotification
{
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)
{
NSDictionary* info = [aNotification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
CGRect activeFieldFrame = activeField.frame;
CGRect scrollViewFrame = scrollView.frame;//used so I can see values below while debugging
CGFloat kbHeight;
if (self.interfaceOrientation == UIInterfaceOrientationPortrait)
{
kbHeight = kbSize.height;
}
else
{
kbHeight = kbSize.width;
}
UIEdgeInsets contentInsets;
contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbHeight, 0.0);
scrollView.contentInset = contentInsets;
scrollView.scrollIndicatorInsets = contentInsets;
// If active text field is hidden by keyboard, scroll it so it's visible
// Your application might not need or want this behavior.
CGRect aRect = self.view.frame;
aRect.size.height -= kbHeight;
activeFieldFrame.origin.y += scrollViewFrame.origin.y+aRect.origin.y;
if (!CGRectContainsRect(aRect, activeFieldFrame) )
{
//add +5 here to give just a little room between field and keyboard
//subtracting scrollviewFrame.origin.y handles cases where scrollview is not at top (0,0) of the view
CGPoint scrollPoint = CGPointMake(0.0, activeFieldFrame.origin.y + activeFieldFrame.size.height + 5 - aRect.size.height - scrollViewFrame.origin.y);
[scrollView setContentOffset:scrollPoint animated:YES];
}
}
}
One interesting thing to note that I ran across is that if you register to get keyboard notifications on view A and pop modal view B over the top of view A and modal view B also needs keyboard shenanigans so you register modal view B to handle them, view A will still get the keyboard event and execute the code even though it is not visible on screen!
The original view A will still get the keyboard will show event if it's part of a tab-based design and another view C that has an edit field which is shown by another tab.
It seems logical that you should unregister listening for keyboard events when your view will disappear and register for events when your view will appear.
Hopefully this helps somebody else.