iOS7中的UITextView会裁剪文本字符串的最后一行

54

iOS7中UITextView表现异常。当您输入并进入UITextView的最后一行时,滚动视图不会像应该滚动到底部,导致文本被“裁剪”。我尝试将其的clipsToBounds属性设置为NO,但它仍然裁剪文本。

我不想调用“setContentOffset:animated”,因为首先:这是非常hacky的解决方案...第二:如果光标在文本框的中间(垂直)位置,将导致不必要的滚动。

以下是截图。

enter image description here

非常感谢任何帮助!

谢谢!


我在设备上也遇到了同样的问题。你找到解决这个问题的方法了吗?谢谢! - Peter Robert
可能是重复问题。我的答案在这里:https://dev59.com/1WMl5IYBdhLWcg3wZWSD#19200023 - andygeers
抱歉回复晚了——我放弃寻找解决方案,最终使用textarea元素嵌入web视图来构建我的“文本视图”。虽然不是我理想的解决方案,但它能够工作 :T。 - ryan
对我来说,在模拟器和设备上,iOS 7.0.3仍存在这个问题。 - bilobatum
显示剩余3条评论
11个回答

93

问题是由于iOS 7引起的。在文本视图委托中,添加以下代码:

- (void)textViewDidChange:(UITextView *)textView {
    CGRect line = [textView caretRectForPosition:
        textView.selectedTextRange.start];
    CGFloat overflow = line.origin.y + line.size.height
        - ( textView.contentOffset.y + textView.bounds.size.height
        - textView.contentInset.bottom - textView.contentInset.top );
    if ( overflow > 0 ) {
        // We are at the bottom of the visible text and introduced a line feed, scroll down (iOS 7 does not do it)
        // Scroll caret to visible area
        CGPoint offset = textView.contentOffset;
        offset.y += overflow + 7; // leave 7 pixels margin
        // Cannot animate with setContentOffset:animated: or caret will not appear
        [UIView animateWithDuration:.2 animations:^{
            [textView setContentOffset:offset];
        }];
    }
}

1
终于找到一个可行的解决方案了!在这个问题上我花了好几个小时,直到找到了这个解决方案... 非常感谢!顺便问一下,你有没有想法这是不是一个 bug?在之前的 iOS 版本中我没有遇到过这个问题。 - Joshua
非常感谢!我希望它能在未来的iOS版本中得到修复。 - Evgenii
1
干得好,伙计!@davidisdk - Dhaval H. Nena
太棒了,正是我所需要的。谢谢。 - asp_net
这是MonoTouch版本的davididsk最优秀解决方案(参见下文)。 - aumansoftware
显示剩余3条评论

22
我找到的解决方案 在这里 是在创建UITextView后添加一行修复代码:
self.textview.layoutManager.allowsNonContiguousLayout = NO;

这一行代码解决了我在iOS7上创建基于UITextView的语法高亮代码编辑器时遇到的三个问题
  1. 编辑时滚动以保持文本可见(本帖子的问题)
  2. 键盘消失后UITextView偶尔会跳来跳去
  3. 尝试滚动视图时UITextView随机滚动跳跃

请注意,当键盘显示/隐藏时,我调整了整个UITextView的大小。


1
什么都没帮助。 - Dmitry
根据文档,"NO"是默认值。 - jkratz
真的很好用,这应该是最佳答案 :) - hardikdevios
除了这个,其他的都对我没用!非常感谢。 对于那些它不起作用的人可能只是有另一个问题。 - boweidmann
"NO" 是默认值,但不知何故它能正常工作。谢谢。 - Maria
似乎在iOS17中仍然可以工作,因为它会切换回Text Kit 1。您还可以使用[UITextView textViewUsingTextLayoutManager:NO]创建一个Text-Kit1的文本视图。 - undefined

6

尝试像这样实现UITextViewDelegate中的-textViewDidChangeSelection:委托方法:

-(void)textViewDidChangeSelection:(UITextView *)textView {
    [textView scrollRangeToVisible:textView.selectedRange];
}

2
这是davidisdk所选答案的修改版。
- (void)textViewDidChange:(UITextView *)textView {
    NSRange selection = textView.selectedRange;

    if (selection.location + selection.length == [textView.text length]) {
        CGRect caretRect = [textView caretRectForPosition:textView.selectedTextRange.start];
        CGFloat overflow = caretRect.origin.y + caretRect.size.height - (textView.contentOffset.y + textView.bounds.size.height - textView.contentInset.bottom - textView.contentInset.top);

        if (overflow > 0.0f) {
            CGPoint offset = textView.contentOffset;
            offset.y += overflow + 7.0f;

            [UIView animateWithDuration:0.2f animations:^{
                [textView setContentOffset:offset];
            }];
        }
    } else {
        [textView scrollRangeToVisible:selection];
    }
}

当textView的内容大小超过边界并且光标不在屏幕上时(例如使用键盘并按箭头键),我遇到了一个bug,导致textView无法动画插入文本。

当最后一行为空时,它在iOS 7.1上无法工作。但是当它不为空时,就没有任何问题了。你有iOS 7.1的解决方法吗? - Dmitry

1

我认为这篇文章提供了关于iOS 7中UITextView滚动/键盘相关问题的最终解决方案。它干净、易读、易用、易于维护,可以轻松地重复使用。

基本技巧: 只需更改UITextView的大小,而不是内容插入!

下面是一个实际操作的例子。假设您有一个基于NIB/Storyboard的UIViewController,使用自动布局,并且UITextView填充整个根视图。如果没有,您需要根据自己的需要调整textViewBottomSpaceConstraint的更改方式。

如何操作:


添加这些属性:
@property (nonatomic, weak) IBOutlet NSLayoutConstraint *textViewBottomSpaceConstraint;
@property (nonatomic) CGFloat textViewBottomSpaceConstraintFromNIB;

请在Interface Builder中连接textViewBottomSpaceConstraint(不要忘记!)

然后在viewDidLoad中:

// Save the state of the UITextView's bottom constraint as set up in your NIB/Storyboard
self.textViewBottomSpaceConstraintFromNIB = self.textViewBottomSpaceConstraint.constant;

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

添加以下方法来处理键盘调整大小(感谢https://github.com/brennanMKE/Interfaces/tree/master/Keyboarding - 这些方法由brennan提供!):

- (void)keyboardWillShowNotification:(NSNotification *)notification {
    CGFloat height = [self getKeyboardHeight:notification forBeginning:TRUE];
    NSTimeInterval duration = [self getDuration:notification];
    UIViewAnimationOptions curve = [self getAnimationCurve:notification];

    [self keyboardWillShowWithHeight:height duration:duration curve:curve];
}

- (void)keyboardWillHideNotification:(NSNotification *)notification {
    CGFloat height = [self getKeyboardHeight:notification forBeginning:FALSE];
    NSTimeInterval duration = [self getDuration:notification];
    UIViewAnimationOptions curve = [self getAnimationCurve:notification];

    [self keyboardWillHideWithHeight:height duration:duration curve:curve];
}

- (NSTimeInterval)getDuration:(NSNotification *)notification {
    NSDictionary *info = [notification userInfo];

    NSTimeInterval duration;

    NSValue *durationValue = [info objectForKey:UIKeyboardAnimationDurationUserInfoKey];
    [durationValue getValue:&duration];

    return duration;
}

- (CGFloat)getKeyboardHeight:(NSNotification *)notification forBeginning:(BOOL)forBeginning {
    NSDictionary *info = [notification userInfo];

    CGFloat keyboardHeight;

    NSValue *boundsValue = nil;
    if (forBeginning) {
        boundsValue = [info valueForKey:UIKeyboardFrameBeginUserInfoKey];
    }
    else {
        boundsValue = [info valueForKey:UIKeyboardFrameEndUserInfoKey];
    }

    UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
    if (UIDeviceOrientationIsLandscape(orientation)) {
        keyboardHeight = [boundsValue CGRectValue].size.width;
    }
    else {
        keyboardHeight = [boundsValue CGRectValue].size.height;
    }

    return keyboardHeight;
}

- (UIViewAnimationOptions)getAnimationCurve:(NSNotification *)notification {
    UIViewAnimationCurve curve = [[notification.userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue];

    switch (curve) {
        case UIViewAnimationCurveEaseInOut:
            return UIViewAnimationOptionCurveEaseInOut;
            break;
        case UIViewAnimationCurveEaseIn:
            return UIViewAnimationOptionCurveEaseIn;
            break;
        case UIViewAnimationCurveEaseOut:
            return UIViewAnimationOptionCurveEaseOut;
            break;
        case UIViewAnimationCurveLinear:
            return UIViewAnimationOptionCurveLinear;
            break;
    }

    return kNilOptions;
}

最后,添加这些方法以对键盘通知做出反应并调整UITextView的大小。
- (void)keyboardWillShowWithHeight:(CGFloat)height duration:(CGFloat)duration curve:(UIViewAnimationOptions)curve
{
    CGFloat correctionMargin = 15; // you can experiment with this margin so the bottom text view line is not flush against the keyboard which doesn't look nice
    self.textViewBottomSpaceConstraint.constant = height + correctionMargin;

    [self.view setNeedsUpdateConstraints];

    [UIView animateWithDuration:duration delay:0 options:curve animations:^{
        [self.view layoutIfNeeded];
    } completion:^(BOOL finished) {

    }];
}

- (void)keyboardWillHideWithHeight:(CGFloat)height duration:(CGFloat)duration curve:(UIViewAnimationOptions)curve
{
    self.textViewBottomSpaceConstraint.constant = self.textViewBottomSpaceConstraintFromNIB;

    [self.view setNeedsUpdateConstraints];

    [UIView animateWithDuration:duration delay:0 options:curve animations:^{
        [self.view layoutIfNeeded];
    } completion:^(BOOL finished) {

    }];
}

同时添加这些方法,以便自动滚动到用户单击的位置。
- (void)textViewDidBeginEditing:(UITextView *)textView
{
    [textView scrollRangeToVisible:textView.selectedRange];
}

- (void)textViewDidChangeSelection:(UITextView *)textView
{
    [textView scrollRangeToVisible:textView.selectedRange];
}

0

这一行导致我看不到最后一行文字:

textView.scrollEnabled = false

尝试移除这个并看看会发生什么...

0
textView.contentInset = UIEdgeInsetsMake(0.0, 0.0, 10.0, 0.0);

这也将解决您的问题


0
如果您正在使用StoryBoard,那么如果您保持了默认的AutoLayout设置,并且没有为UITextView设置顶部/底部约束,那么这种行为也可能发生。请检查文件检查器以查看您的AutoLayout状态是什么...

0

这是 davididsk 最优秀的解决方案(来自上面)的 MonoTouch 版本。

TextView.SelectionChanged += (object sender, EventArgs e) => {
                TextView.ScrollRangeToVisible(TextView.SelectedRange);
            };


            TextView.Changed += (object sender, EventArgs e) => {

                CGRect line = TextView.GetCaretRectForPosition(TextView.SelectedTextRange.Start);
                nfloat overflow = line.Y + line.Height - 
                                     (TextView.ContentOffset.Y + 
                                      TextView.Bounds.Height -          
                                      TextView.ContentInset.Bottom -
                                      TextView.ContentInset.Top );
                if ( overflow > 0 ) 
                {
                    // We are at the bottom of the visible text and introduced 
                    // a line feed, scroll down (iOS 7 does not do it)
                    // Scroll caret to visible area
                    CGPoint offset = TextView.ContentOffset;
                    offset.Y+= overflow + 7; // leave 7 pixels margin
                    // Cannot animate with setContentOffset:animated: 
                    // or caret will not appear
                    UIView.Animate(0.1,()=> {
                        TextView.ContentOffset = offset;
                    });
                }
            };

-1
   textView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

这对我解决了问题


这不是作者想要实现的目标。正如@Softlion所提到的,这会禁用自动布局。 - regetskcob

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