I encountered a similar problem, including the "If I set animated to NO, everything works as expected" part.
It turned out that on iOS 6 the UITextView auto scrolls its nearest parent UIScrollView to make the cursor visible when it becomes first responder. On iOS 7 there is no such behavior. The UIScrollView seems to get confused by two calls to to scrollRectToVisible at about the same time.
On iOS 6 my explicit call to scrollRectToVisible is ignored most of the time. It will only scroll to make the first line of the UITextView visible (the auto scroll) and not the whole thing as it does on iOS 7.
To test it, make a new single view app in Xcode 5, set its deployment target to 6.0 and use the code below for the ViewController.m. Run it in the iOS 6.1 simulator, scroll to make the UITextView hidden and tap anywhere on the screen. You might have to retry it a few times, but in most cases it will only make the first line visible. If you re-enable the WORKAROUD define the UITextView gets embedded in its own UIScrollView and the call to scrollRectToVisible works as expected.
#import "ViewController.h"
//#define WORKAROUND
@interface ViewController ()
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) UITextView *textView;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self.view addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(viewTap)]];
self.scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 320, 240)];
self.scrollView.contentSize = CGSizeMake(320, 400);
self.scrollView.backgroundColor = [UIColor lightGrayColor];
[self.view addSubview:self.scrollView];
#ifdef WORKAROUND
UIScrollView* dummyScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(20, 280, 280, 100)];
self.textView = [[UITextView alloc] initWithFrame:dummyScrollView.bounds];
[dummyScrollView addSubview:self.textView];
[self.scrollView addSubview:dummyScrollView];
#else
self.textView = [[UITextView alloc] initWithFrame:CGRectMake(20, 280, 280, 100)];
[self.scrollView addSubview:self.textView];
#endif
self.textView.backgroundColor = [UIColor grayColor];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWasShown:) name:UIKeyboardDidShowNotification object:nil];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)viewTap
{
if (self.textView.isFirstResponder) {
[self.textView resignFirstResponder];
}
else {
[self.textView becomeFirstResponder];
}
}
- (void)keyboardWasShown:(NSNotification*)aNotification
{
#ifdef WORKAROUND
[self.scrollView scrollRectToVisible:CGRectInset(self.textView.superview.frame, 0, -10) animated:YES];
#else
[self.scrollView scrollRectToVisible:CGRectInset(self.textView.frame, 0, -10) animated:YES];
#endif
}
@end