如何使UIScrollView滚动到UITextView的光标位置?

9
我有一个视图,类似于笔记应用程序——即在有线纸上输入。为了使文本和纸张同时滚动,我已经禁用了UITextView的滚动,并将我的UITextView和UIImageView放置在UIScrollView内。
唯一的问题是,当用户输入时,文本会消失在键盘下面,因为显然UIScrollView不知道要滚动到光标位置。
有没有简单的方法可以获取光标位置并告诉UIScrollView滚动到那里?
---编辑---
从类似于这里(某人试图在UITableView中做类似的事情)的东西开始,我已经成功制作了一个具有固定背景的可增长可编辑UITextView,几乎完美地滚动。现在唯一的问题是:
  1. 如果用户输入速度很快,文本向上移动时会出现轻微的抖动。
  2. 如果用户隐藏键盘,在屏幕底部选择文本,然后再次显示键盘,他们必须输入几个字母才能再次看到文本 - 它不会立即滚动。
  3. 当用户隐藏键盘时,滚动视图的框架填充屏幕的动画感觉不太对劲。

这是代码 - 如果有人能进一步完善它,我将非常感激...

#import "NoteEditViewController.h"
#import "RLWideLabelTableCell.h"

@implementation NoteEditViewController
@synthesize keyboardSize;
@synthesize keyboardHideDuration;
@synthesize scrollView;
@synthesize noteTextView;

//
// Dealloc and all that stuff
//
- (void)loadView
{
    [super loadView];
    UIScrollView *aScrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
    self.scrollView = aScrollView; [aScrollView release];
    self.scrollView.contentSize = CGSizeMake(self.view.frame.size.width, noteTextView.frame.size.height);
    [self.view addSubview:scrollView];
}

- (void)viewDidLoad
{   
    [super viewDidLoad];

    // Get notified when keyboard is shown. Don't need notification when hidden because we are
    // using textViewDidEndEditing so we can start animating before the keyboard disappears.
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWasShown:)
                                                 name:UIKeyboardDidShowNotification object:nil];

    // Add the Done button so we can test dismissal of the keyboard    
    UIBarButtonItem *doneButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone 
        target:self
        action:@selector(doneButton:)];
    self.navigationItem.rightBarButtonItem = doneButton; [doneButton release];

    // Add the background image that will scroll with the text
    CGRect noteImageFrame = CGRectMake(self.view.bounds.origin.x, 
                                       noteTitleImageFrame.size.height, 
                                       self.view.bounds.size.width, 500);    

    UIView *backgroundPattern = [[UIView alloc] initWithFrame:noteImageFrame];
    backgroundPattern.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"Notepaper-iPhone-Line"]];
    [self.scrollView addSubview:backgroundPattern];
    [self.view sendSubviewToBack:backgroundPattern];
    [backgroundPattern release];

    // Add the textView
    CGRect textViewFrame = CGRectMake(noteImageFrame.origin.x+27, 
                                      noteImageFrame.origin.y-3, 
                                      noteImageFrame.size.width-35,
                                      noteImageFrame.size.height);

    RLTextView *textView = [[RLTextView alloc] initWithFrame:textViewFrame];
    self.noteTextView = textView; [textView release];
    self.noteTextView.font = [UIFont fontWithName:@"Cochin" size:21];
    self.noteTextView.backgroundColor = [UIColor clearColor];
    self.noteTextView.delegate = self;
    self.noteTextView.scrollEnabled = NO;
    [self.scrollView addSubview:self.noteTextView];
}

- (void)doneButton:(id)sender
{
    [self.view endEditing:TRUE];
}

// When the keyboard is shown, the UIScrollView's frame shrinks so that it fits in the
// remaining space
- (void)keyboardWasShown:(NSNotification*)aNotification
{
    NSDictionary* info = [aNotification userInfo];
    CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
    float kbHideDuration = [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue];
    self.keyboardHideDuration = kbHideDuration;
    self.keyboardSize = kbSize;
    self.scrollView.frame = CGRectMake(self.view.bounds.origin.x, 
                                       self.view.bounds.origin.y, 
                                       self.view.bounds.size.width, 
                                       self.view.bounds.size.height - kbSize.height);    
}

// When the user presses 'done' the UIScrollView expands to the size of its superview
// again, as the keyboard disappears.
- (void)textViewDidEndEditing:(UITextView *)textView
{
    [UIScrollView animateWithDuration:keyboardHideDuration animations:^{self.scrollView.frame = self.view.bounds;}];
}

// This method needs to get called whenever there is a change of cursor position in the text box
// That means both textViewDidChange: and textViewDidChangeSelection:
- (void)scrollToCursor
{
    // if there is a selection cursor…
    if(noteTextView.selectedRange.location != NSNotFound) {
        NSLog(@"selectedRange: %d %d", noteTextView.selectedRange.location, noteTextView.selectedRange.length);

        // work out how big the text view would be if the text only went up to the cursor
        NSRange range;
        range.location = noteTextView.selectedRange.location;
        range.length = noteTextView.text.length - range.location;
        NSString *string = [noteTextView.text stringByReplacingCharactersInRange:range withString:@""];
        CGSize size = [string sizeWithFont:noteTextView.font constrainedToSize:noteTextView.bounds.size lineBreakMode:UILineBreakModeWordWrap];

        // work out where that position would be relative to the textView's frame
        CGRect viewRect = noteTextView.frame;  
        int scrollHeight = viewRect.origin.y + size.height;
        CGRect finalRect = CGRectMake(1, scrollHeight, 1, 1);

        // scroll to it
        [self.scrollView scrollRectToVisible:finalRect animated:YES];
    }
}

// Whenever the text changes, the textView's size is updated (so it grows as more text
// is added), and it also scrolls to the cursor.
- (void)textViewDidChange:(UITextView *)textView
{
    noteTextView.frame = CGRectMake(noteTextView.frame.origin.x, 
                                    noteTextView.frame.origin.y, 
                                    noteTextView.frame.size.width, 
                                    noteTextView.contentSize.height);
    self.scrollView.contentSize = CGSizeMake(self.scrollView.contentSize.width, 
                                             noteTextView.frame.size.height+200);
    [self scrollToCursor];
}

// The textView scrolls to the cursor whenever the user changes the selection point.
- (void)textViewDidChangeSelection:(UITextView *)aTextView 
{
    [self scrollToCursor];
}

// PROBLEM - the textView does not scroll until the user starts typing - just selecting
// it is not enough. 
- (void)textViewDidBeginEditing:(UITextView *)textView
{
    [self scrollToCursor];
}
3个回答

6
很高兴您找到了我的关于it技术的帖子,并且很高兴它对您有所帮助!
我认为您可能没有看到底线,因为有这一行代码:
CGRect finalRect = CGRectMake(1, scrollHeight, 1, 1);

您正在创建一个1x1点的框。单行文本可能高达20或30个点(取决于字体大小)。因此,如果您将此点滚动到可见状态,它可能仅显示底部行的顶部像素-使底部行实际上变得不可见!如果您使finalRect稍微高一些,以覆盖整行,它可能会更好地工作:
CGRect finalRect = CGRectMake(1, scrollHeight, 1, 30);

此外,您可能会一次调用多次scrollRectToVisible代码,这可能会导致"抖动"。在我的代码中,我仅从textViewDidChangeSelection运行scrollRectToVisible,并在textViewDidChange中调整UITextView的大小(如果需要)。UIScrollView(以及通过继承的UITableView)具有内置支持,可以滚动活动选择的元素可见,在我的测试中,当仅在键入时调整UITextView的大小时效果良好(但在触摸内部的特定点时不行)。

谢谢,Manesh!恰好我刚按照你说的更改了finalRect参数,这使得文字滚动到视图中更整洁。但是我所指的“底线”问题略有不同:如果我点击屏幕的下半部分使textView处于活动状态,当键盘出现时它根本不会滚动,直到我输入一个字母或移动光标才会触发滚动。我猜这是因为在调用 textViewDidBeginEditing 时还没有设置好某些东西,所以 scrollToCursor 方法无法正常工作。 - Ric Levy
你能否提供一个带有可工作代码的示例项目?另外,如果你想继续尝试自行调试,我建议在每个函数中放置NSLog语句并观察它们的触发顺序,这可能会提供一些见解。根据你如何显示和隐藏键盘,某些函数可能会触发或不触发。 - Manesh
谢谢Manesh - 我发现了一个更简单的解决方案。由于UITextView是UIScrollView的子类,我可以在scrollViewDidScroll:代理方法中放置代码来移动背景图像。因此,我只需让我的UITextView正常滚动,而单独的背景视图会随之移动。这样,滚动行为就是完美的,因为它是UITextView内置的行为。感谢您的所有帮助! - Ric Levy

1

UITextView中找到任何文本或光标的屏幕坐标并不容易。

你应该注册UIKeyboardWillShowNotificationUIKeyboardWillShowNotification。在回调函数中,你可以调整UIScrollViewsizecontentInsets以适应键盘的大小。

键盘的大小,甚至动画持续时间都在通知的userInfo中提供,因此你可以以漂亮的动画方式完成它。

你可以在这里找到更多信息和示例代码:http://developer.apple.com/library/ios/#documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/KeyboardManagement/KeyboardManagement.html


1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接