确定 UIScrollVIew 中 UITextView 的滚动偏移量

Posted

技术标签:

【中文标题】确定 UIScrollVIew 中 UITextView 的滚动偏移量【英文标题】:Determine scroll offset for UITextView in UIScrollVIew 【发布时间】:2013-08-08 09:20:14 【问题描述】:

我在UIScrollView 的 textView 下方和之后有 UITextView 和其他视图。 当用户输入文本时,我会更改 textView.contentSize.height 上的 textView 框架高度。 这个想法是为用户保存所有文本和其他元素的可能性。 我有一些问题。当用户点击我想滚动到这一行。这一定是这样的:

我无法确定如何计算偏移量并始终滚动到当前插入符号。

我的初始画面:

所有视图都是UIScrollView 的子视图。 每次用户输入 @"\n" 时,我都会更改 UITextView 框架:

CGRect textViewFrame = textView.frame;
CGSize textViewContentSize = textView.contentSize;
NSLog(@"textView content size: %@", NSStringFromCGSize(textViewContentSize));
textViewFrame.size.height = textViewContentSize.height;
[textView setFrame:textViewFrame];

然后我增加UIScrollView contentSize。 我的问题 - 我不明白如何在键盘下滚动到 textView 的 CURSOR。 我尝试做一些事情:

CGPoint cursorPosition = [textView caretRectForPosition:textView.selectedTextRange.start].origin;

CGPoint relativeCursorPoint = [textView convertPoint:cursorPosition toView:scrollView];

if(textView.isFirstResponder && relativeCursorPoint.y >= scrollViewFrame.size.height + scrollView.contentOffset.y)

    int offset = relativeCursorPoint.y - scrollViewFrame.size.height + 18.0f;
    //int offset = textViewRect.origin.y - scrollViewFrame.size.height + 18.0f;
    [scrollView setContentOffset:CGPointMake(0, offset) animated:YES];

但是没有用。

【问题讨论】:

您是否在问如何将视图滚动到键盘上方?我发现有点难以理解你的问题,你能澄清一下吗?谢谢! @NinjaLikesCheez,是的,像这样。 文本的长度(你在TextField中输入的)几乎等于TextField的高度和滚动。 @G.Ganesh, :) 我询问了 textView 和 textView 在 scrollView 中的作用 【参考方案1】:

A.) 为了实现与 Apple Notes 滚动 + 键盘关闭类似的效果,我首先在 UIScrollView 中添加了一个 UITextView。

B.) 然后添加 self 作为键盘变化的观察者:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textViewDidBeginEditing:) name:UITextViewTextDidBeginEditingNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self  selector:@selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil];

C.) 然后当用户点击 textview / textfield 时,会调用下面的方法。您获取键盘的开始高度和结束高度,并将名为keyboardHeight 的变量设置为endHeight 值(稍后使用)。

下面计算的“差异”用于在用户上下移动自动更正/建议栏时上下移动文本。如果你不想要这个,你可以将 textView 上的 'autocorrectionType' 设置为 false。

-(void)keyboardWillChangeFrame:(NSNotification *)n 

    float beginHeight = [[n.userInfo valueForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size.height;
    float endHeight = [[n.userInfo valueForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height;
    float difference = beginHeight-endHeight;
    keyBoardHeight = endHeight; //set the var

    //animate change
    if (difference != 0) //animate frame change in your textview 

D.) 在 textViewDidBegin 方法上,将 UIScrollview 移动到屏幕顶部,使 textview 与顶部齐平(setContentOffset)。

您计算 textview 的高度,因为它应该在视图顶部和键盘顶部之间,下面称为“initialTVHeight”。

您应该强制 textview 启用其滚动,因此将 textView.contentSize 设置为最小尺寸 (initialTVHeight) + 一个像素 - 这会强制滚动。

-(void)textViewDidBeginEditing:(UITextView *)textView 

    //need to move the tv up to the top
    float yOff = 0;
    yOff += durationCell.frame.size.height;
    yOff += reminderCell.frame.size.height;
    yOff += fixedCell.frame.size.height;
    yOff += repeatCell.frame.size.height;
    [mainScroller setContentOffset:CGPointMake(0, yOff) animated:true];

    //resize the textview to meet the top of the keyboard
    float padding = 0; //padding between keyboard and last line in the textview
    initialTVHeight = h - 60 - keyBoardHeight - padding; //the height when text does not overflow
    textTV.frame = CGRectMake(0, textTV.frame.origin.y, w, initialTVHeight); //set the frame to that size

    //if smaller than minimum, increase (if editing existing text)
    if (textTV.contentSize.height < initialTVHeight)
       textTV.contentSize = CGSizeMake(w, initialTVHeight+1); //force initial scroll
    

E.) 此方法将 textView contentSize 更新为最小高度以确保启用滚动(即,如果只有两行文本,用户仍然可以反弹滚动)。 textView 内容长于初始大小的地方,让它自然增长。

-(void)textViewDidChange:(UITextView *)textView 
    //require minimum height for scroll
    if (textTV.contentSize.height < initialTVHeight) //content height comes in under, force scroll
        textTV.contentSize = CGSizeMake(w, initialTVHeight+1); //adding one forces scroll
    

F.) 添加这些方法,确保仅通过用户拖动(而不是用户输入新行)关闭键盘

-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView 
    if ([scrollView isEqual:textTV])
        enableDragDismiss = true;
    

-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
    if ([scrollView isEqual:textTV])
        enableDragDismiss = false;
    

G.) 最后,添加这个方法,如果用户拖动到键盘高度以下,它会关闭键盘。

-(void)scrollViewDidScroll:(UIScrollView *)scrollView 

    if (enableDragDismiss && [scrollView isEqual:textTV])

        float y = [textTV.panGestureRecognizer locationInView:self.view].y;
        float keyboardY = h-keyBoardHeight;

        if (y > keyboardY)
            enableDragDismiss = false; //allow only once
            [textTV resignFirstResponder];
            [UIView animateWithDuration:0.5f
                                  delay:0.0f
                 usingSpringWithDamping:1.0f
                  initialSpringVelocity:0.8f
                                options:UIViewAnimationOptionCurveEaseOut
                             animations:^
                                 mainScroller.contentOffset = CGPointMake(0, 0);
                                 textTV.contentOffset = CGPointMake(0, 0);
                             
                             completion:^(BOOL finished)
                             ];

            float yOff = 60;
            yOff += durationCell.frame.size.height;
            yOff += reminderCell.frame.size.height;
            yOff += fixedCell.frame.size.height;
            yOff += repeatCell.frame.size.height;
            textTV.frame = CGRectMake(0, textTV.frame.origin.y, w, h-yOff);
        
    

【讨论】:

【参考方案2】:

好的,如果您尝试将 UITextView 滚动到键盘“上方”,那么您可以使用 -[UITextView textViewDidBeginEditing:] 在初始触摸时修改您的 scrollView 的内容偏移量,但是如果您希望每次用户都滚动添加换行符或类似内容,您应该可以使用-[UITextView shouldChangeTextInRange:],如下所示。

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text

    if ([text isEqualToString:@"\n"]) 
        // User has pressed 'return' and entered a new line
        // change the content offset + height of line text
        self.textView.contentOffset = CGPointMake(x, y);


        return NO;
    

    return YES;

UITextView 继承自 UIScrollView,因此您可以像在 UIScrollView 中一样更改内容偏移量。

【讨论】:

【参考方案3】:

试试这个,它对我有用

- (void)viewDidLoad
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWasShown:) name:UIKeyboardDidShowNotification object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];

-(BOOL)textFieldShouldReturn:(UITextField *)textField
    [textField resignFirstResponder];
    return YES;
    

- (void)textFieldDidBeginEditing:(UITextField *)textField
    
        activeField=textField;
    

    -(void)keyboardWasShown:(NSNotification *)notification
        float viewWidth = 1024;
        float viewHeight = 654;
        NSDictionary *keyboardInfo = [notification userInfo];
        CGSize keyboardSize = [[keyboardInfo objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
        float keyboardHeight = keyboardSize.width;
        CGRect viewableAreaFrame = CGRectMake(0.0, 0.0, viewWidth, viewHeight - keyboardHeight);
        CGRect txtDemoFrame = [activeField.superview convertRect:activeField.frame toView:self.view];
        if (!CGRectContainsRect(viewableAreaFrame, txtDemoFrame)) 
            // We need to calculate the new Y offset point.
            //float scrollPointY = viewHeight - keyboardHeight;
            // Don't forget that the scroller should go to its original position
            // so store the current Y point of the content offset.
            self.originalScrollerOffsetY = self.SCRVMain.contentOffset.y;
            // Finally, scroll.
            [self.SCRVMain setContentOffset:CGPointMake(0.0, [activeField.superview convertPoint:activeField.frame.origin toView:self.view].y-200) animated:YES];
        
    
    -(void)keyboardWillHide:(NSNotification *)notification
        UIEdgeInsets contentInsets = UIEdgeInsetsZero;
        self.SCRVMain.contentInset = contentInsets;
        self.SCRVMain.scrollIndicatorInsets = contentInsets;
        [self.SCRVMain setContentOffset:CGPointMake(0.0, self.originalScrollerOffsetY) animated:YES];
    

【讨论】:

【参考方案4】:

在我的 textViewDidChange 中:我已经实现了这样的解决方案(为非常原始的代码道歉;)):

NSString* substringToSelection = [textView.text substringToIndex:textView.selectedRange.location];
UIFont* textFont = textView.font;

CGRect boundingRect = [substringToSelection boundingRectWithSize:CGSizeMake(textView.frame.size.width, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@NSFontAttributeName : textFont context:nil];
CGRect translatedRect = [textView convertRect:boundingRect toView:self.scrollView];

if (!CGRectContainsPoint(self.scrollView.bounds, CGPointMake(CGRectGetMaxX(translatedRect), CGRectGetMaxY(translatedRect))))

    [self.scrollView scrollRectToVisible:translatedRect animated:YES];

【讨论】:

我喜欢这个解决方案,但有一个评论和问题:首先,可能不需要 CGRectContainsPoint() 检查,因为如果该区域已经可见,-scrollRectToVisible:animated: 方法不会执行任何操作。其次,该解决方案似乎向下滚动刚好足以使该区域的顶部可见。在我的情况下(在 ios7 上),这显示了行的前几个像素(光标所在的位置)而不是整行。您是否也看到了这种行为?我通过添加来解决这个问题:translatedRect.origin.y += textFont.capHeight;

以上是关于确定 UIScrollVIew 中 UITextView 的滚动偏移量的主要内容,如果未能解决你的问题,请参考以下文章

确定 UIScrollVIew 中 UITextView 的滚动偏移量

View 生命周期中确定 UIScrollView 屏幕大小的最佳时间

如何在分页 UIScrollView 中显示确定的项目,而不是仅从 0 加载它

UIScrollView 在分页时确定 scrollViewDidEndDragging 上的滚动方向

让 2 个不同的 UIView 确定 UIScrollView 的 contentView 的高度

UIScrollView 通知