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

Posted

技术标签:

【中文标题】如何让 UIScrollView 滚动到 UITextView 的光标位置?【英文标题】:How can I make a UIScrollView scroll to a UITextView's cursor position? 【发布时间】:2011-05-11 14:45:14 【问题描述】:

我有一个类似于便笺应用程序的视图 - 即在有横线的纸上打字。为了使文本和纸张同时滚动,我禁用了 UITextView 的滚动,而是将 UITextView 和 UIImageView 都放在 UIScrollView 中。

唯一的问题是,当用户键入时,文本在键盘下方消失,因为显然 UIScrollView 不知道滚动到光标位置。

有什么简单的方法可以检索光标位置并告诉 UIScrollView 滚动到那里吗?

---编辑---

从类似的here(有人试图用 UITableView 做类似的事情)开始,我设法制作了一个不断增长的、可编辑的 UITextView,它具有几乎完美滚动的固定背景。现在唯一的问题是:

    如果用户输入特别快,文本向上移动时会出现轻微抖动。 如果用户隐藏键盘,选择屏幕底部的文本,然后再次显示键盘,他们必须键入几个字母才能再次显示文本 - 它不会立即向上滚动。 当用户隐藏键盘时,滚动视图的框架填充屏幕时的动画感觉不太对劲。

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

#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];

【问题讨论】:

【参考方案1】:

很高兴你找到了我的帖子,很高兴它对你有帮助!

我相信您可能因为这条线而看不到底线:

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 的大小时效果很好(但在通过触摸选择内部的特定点时则不行)。

【讨论】:

谢谢,马内什!碰巧我刚刚完全按照您所说的那样更改了 finalRect 参数,这使得文本滚动到视图中更加整齐。但我所指的“底线”问题略有不同:如果我点击屏幕的下半部分以激活 textView,当键盘出现时它不会滚动根本 -我必须在滚动触发之前输入一个字母或移动光标。我猜这是因为调用 textViewDidBeginEditing 时还没有设置某些东西,所以 scrollToCursor 方法不起作用。 您可以发布一个带有工作代码的示例项目吗?此外,如果您想继续尝试自己调试它,我会考虑将 NSLog 语句放在每个函数中并观察它们触发的顺序,它可能会提供一些见解。某些功能可能会触发也可能不会触发,具体取决于您显示和隐藏键盘的方式。 感谢 Manesh - 碰巧我找到了一个更简单的解决方案。由于 UITextView 是 UIScrollView 的子类,我只需将代码放在scrollViewDidScroll: 委托方法中即可移动背景图像。所以我只是让我的 UITextView 正常滚动,并且单独的背景视图响应它移动。这样,滚动行为就完美了,因为它是 UITextView 的内置行为。感谢大家的帮助!【参考方案2】:

没有简单的方法可以在UITextView 中找到任何文本或光标的屏幕坐标。

您应该做的是注册UIKeyboardWillShowNotificationUIKeyboardWillShowNotification。并在回调中调整UIScrollViewsizecontentInsets 以调整键盘的大小。

通知userInfo中提供了键盘的大小,甚至动画持续时间,因此您可以以漂亮的动画方式进行操作。

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

【讨论】:

【参考方案3】:

严格来说不是对您的问题的回答,但这里有一种不同的方法来处理带注释的背景技巧:http://www.cocoanetics.com/2010/03/stuff-you-learn-from-reverse-engineering-notes-app/

我用过,效果很好。

【讨论】:

以上是关于如何让 UIScrollView 滚动到 UITextView 的光标位置?的主要内容,如果未能解决你的问题,请参考以下文章

如何让 UIScrollView 滚动

如何在 UIScrollView 中同时拖动和移动某些东西并滚动

如何正确设置 UIScrollView 高度

IOS如何让iCarousel像UIScrollView页面一样滚动

如何让 UIScrollView 适合其内容大小,直到在自动布局中滚动之前达到最大值

如何在 UIScrollView 中将 UIToolbar 固定到屏幕底部