【问题标题】:iOS 7.1 UITextView still not scrolling to cursor/caret after new lineiOS 7.1 UITextView 在换行后仍未滚动到光标/插入符号
【发布时间】:2014-04-14 11:25:58
【问题描述】:

从 iOS 7 开始,UITextView 不会在用户键入换行的文本时自动滚动到光标处。这个问题在 SO 和其他地方都有很好的记录。对我来说,这个问题在 iOS 7.1 中仍然存在。我做错了什么?

我安装了 Xcode 5.1 并针对 iOS 7.1。我正在使用自动布局。

这是我如何将文本视图的内容定位在键盘上方:

- (void)keyboardUp:(NSNotification *)notification
{
    NSDictionary *info = [notification userInfo];
    CGRect keyboardRect = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
    keyboardRect = [self.view convertRect:keyboardRect fromView:nil];

    UIEdgeInsets contentInset = self.textView.contentInset;
    contentInset.bottom = keyboardRect.size.height;
    self.textView.contentInset = contentInset;
}

我尝试过的:我已经尝试了许多针对此问题发布到 SO 的解决方案,因为它与 iOS 7 相关。我尝试过的所有解决方案都没有 strong> 对于显示属性字符串的文本视图似乎很有效。在以下三个步骤中,我概述了 SO (https://stackoverflow.com/a/19277383/1239263) 上投票最多的答案如何响应用户第一次点击返回键。

(1.) 文本视图成为viewDidLoad 中的第一响应者。滚动到光标所在文本视图的底部。

(2.) 在输入单个字符之前,点击键盘上的返回键。插入符号消失在视线之外。

(3.) 然而,再次点击返回键似乎使情况正常化。 (注意:删除后一个新行会使插入符号再次消失)。

【问题讨论】:

  • 还应该注意的是,升级到 iOS 7.1 后,这个滚动问题仍然存在于 Apple 的日历应用程序中。创建一个新事件,向下滚动到“备注”部分,反复按回车键直到光标消失。
  • 你没有做错什么。这是一个错误。
  • @matt 但是有很多关于这个问题的错误报告提交给苹果。也许不应该设置由 Text Kit 支持的文本视图的 contentInset。如果我避免设置 contentInset,则所谓的错误大多会消失。
  • @bilobatum 查看 peter steinburgers 的帖子,这正是他用很好的解释解决的问题 - petersteinberger.com/blog/2014/fixing-uitextview-on-ios-7
  • 该问题在 iOS 8 上已修复。

标签: ios ios7 uitextview ios7.1


【解决方案1】:

UITextView 后代类的改进解决方案代码:

#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
#define is_iOS7 SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"7.0")
#define is_iOS8 SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")

@implementation MyTextView {
    BOOL settingText;
}

- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleTextViewDidChangeNotification:) name:UITextViewTextDidChangeNotification object:self];
    }
    return self;
}

- (void)scrollToCaretInTextView:(UITextView *)textView animated:(BOOL)animated {
    CGRect rect = [textView caretRectForPosition:textView.selectedTextRange.end];
    rect.size.height += textView.textContainerInset.bottom;
    [textView scrollRectToVisible:rect animated:animated];
}

- (void)handleTextViewDidChangeNotification:(NSNotification *)notification {
    if (notification.object == self && is_iOS7 && !is_iOS8 && !settingText) {
        UITextView *textView = self;
        if ([textView.text hasSuffix:@"\n"]) {
            [CATransaction setCompletionBlock:^{
                [self scrollToCaretInTextView:textView animated:NO];
            }];
        } else {
            [self scrollToCaretInTextView:textView animated:NO];
        }
    }
}

- (void)setText:(NSString *)text {
    settingText = YES;
    [super setText:text];
    settingText = NO;
}

注意在蓝牙键盘上按下 Down 键时不起作用。

【讨论】:

  • 我喜欢它(尽管您的初始化程序不完整)。大概 setAttributedText: 也必须被覆盖。我很高兴 iOS 8 修复了这个错误。
  • 初始化程序已修复。
【解决方案2】:

稳健的解决方案应在以下情况下成立:

(1.) 显示属性字符串的文本视图

(2.) 通过点击键盘上的返回键创建一个新行

(3.) 通过键入溢出到下一行的文本创建一个新行

(4.) 复制和粘贴文本

(5.) 第一次按回车键创建的新行(参见 OP 中的 3 个步骤)

(6.) 设备旋转

(7.) 在某些情况下,我想不出你会……

为了在 iOS 7.1 中满足这些要求,似乎仍然需要手动滚动到插入符号。

当调用文本视图委托方法 textViewDidChange: 时,通常会看到手动滚动到插入符号的解决方案。但是,我发现这种技术不能满足上面的情况#5。即使在滚动到插入符号之前拨打layoutIfNeeded 也无济于事。相反,我不得不滚动到 CATransaction 完成块内的插入符号:

// this seems to satisfy all of the requirements listed above–if you are targeting iOS 7.1
- (void)textViewDidChange:(UITextView *)textView
{
    if ([textView.text hasSuffix:@"\n"]) {

        [CATransaction setCompletionBlock:^{
            [self scrollToCaretInTextView:textView animated:NO];
        }];

    } else {
        [self scrollToCaretInTextView:textView animated:NO];
    }
}

为什么会这样?我不知道。你得问苹果工程师。

为了完整起见,这里是与我的解决方案相关的所有代码:

#import "ViewController.h"

@interface ViewController () <UITextViewDelegate>

@property (weak, nonatomic) IBOutlet UITextView *textView; // full-screen

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    NSString *string = @"All work and no play makes Jack a dull boy.\n\nAll work and no play makes Jack a dull boy. All work and no play makes Jack a dull boy. All work and no play makes Jack a dull boy. All work and no play makes Jack a dull boy. All work and no play makes Jack a dull boy. All work and no play makes Jack a dull boy. All work and no play makes Jack a dull boy. All work and no play makes Jack a dull boy.";

    NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:string attributes:@{NSFontAttributeName: [UIFont fontWithName:@"Verdana" size:30.0]}];

    self.textView.attributedText = attrString;

    self.textView.delegate = self;
    self.textView.backgroundColor = [UIColor yellowColor];
    [self.textView becomeFirstResponder];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardIsUp:) name:UIKeyboardDidShowNotification object:nil];
}

// helper method
- (void)scrollToCaretInTextView:(UITextView *)textView animated:(BOOL)animated
{
    CGRect rect = [textView caretRectForPosition:textView.selectedTextRange.end];
    rect.size.height += textView.textContainerInset.bottom;
    [textView scrollRectToVisible:rect animated:animated];
}

- (void)keyboardIsUp:(NSNotification *)notification
{
    NSDictionary *info = [notification userInfo];
    CGRect keyboardRect = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
    keyboardRect = [self.view convertRect:keyboardRect fromView:nil];

    UIEdgeInsets inset = self.textView.contentInset;
    inset.bottom = keyboardRect.size.height;
    self.textView.contentInset = inset;
    self.textView.scrollIndicatorInsets = inset;

    [self scrollToCaretInTextView:self.textView animated:YES];
}

- (void)textViewDidChange:(UITextView *)textView
{
    if ([textView.text hasSuffix:@"\n"]) {

        [CATransaction setCompletionBlock:^{
            [self scrollToCaretInTextView:textView animated:NO];
        }];

    } else {
        [self scrollToCaretInTextView:textView animated:NO];
    }
}

@end

如果您发现这不起作用,请告诉我。

【讨论】:

  • 您的解决方案不适用于蓝牙键盘上的向下键。
  • 它破坏了 setText 方法 - 如果新文本有 @"\n" 后缀,文本总是会滚动到末尾。
【解决方案3】:

我通过获取插入符号的实际位置并对其进行调整来解决它,这是我的方法:

- (void) alignTextView:(UITextView *)textView withAnimation:(BOOL)shouldAnimate {

    // where the blinky caret is
    CGRect caretRect = [textView caretRectForPosition:textView.selectedTextRange.start];
    CGFloat offscreen = caretRect.origin.y + caretRect.size.height - (textView.contentOffset.y + textView.bounds.size.height - textView.contentInset.bottom - textView.contentInset.top);

    CGPoint offsetP = textView.contentOffset;
    offsetP.y += offscreen + 3; // 3 px -- margin puts caret 3 px above bottom

    if (offsetP.y >= 0) {
        if (shouldAnimate) {
            [UIView animateWithDuration:0.2 animations:^{
                [textView setContentOffset:offsetP];
            }];
        }
        else {
            [textView setContentOffset:offsetP];
        }
    }
}

如果只需要在用户按回车/回车后进行定位,试试:

- (void) textViewDidChange:(UITextView *)textView {
    if ([textView.text hasSuffix:@"\n"]) {
        [self alignTextView:textView withAnimation:NO];
    }
}

让我知道它是否适合你!

【讨论】:

  • @bilobatum 听起来您的解决方案只是部分解决方案。尽管存在操作系统错误,文本视图仍应始终显示光标。此外,使用 contentInset 比更改 IMO 视图框架更容易。
  • 如果你只在用户按下回车时使用插入符号怎么办,请参阅编辑。
  • @Logan 不起作用,因为在调用 textView:shouldChangeTextInRange:replacementText: 时,文本视图中尚未设置换行符。
  • @Logan 好的,那行得通。我会给你+1,但我会推迟选择答案。在 iOS 7.1 中,我真的希望避免手动滚动黑客来做一些在 iOS 6 中免费提供的东西。
  • 希望有什么事情发生!
【解决方案4】:

我找不到原始来源,但它适用于 iOS7.1

 - (void)textViewDidChangeSelection:(UITextView *)textView
 {
   if ([textView.text characterAtIndex:textView.text.length-1] != ' ') {
        textView.text = [textView.text stringByAppendingString:@" "];
    }

    NSRange range0 = textView.selectedRange;
    NSRange range = range0;
    if (range0.location == textView.text.length) {
        range = NSMakeRange(range0.location - 1, range0.length);
    } else if (range0.length > 0 &&
               range0.location + range0.length == textView.text.length) {
        range = NSMakeRange(range0.location, range0.length - 1);
    }
    if (!NSEqualRanges(range, range0)) {
        textView.selectedRange = range;
    }
 }

【讨论】:

  • 您是否尝试过使用显示属性字符串的文本视图的此解决方案?我无法让它工作。
【解决方案5】:

有人制作了一个子类来解决UITextView 中所有与滚动相关的问题。实现再简单不过了 - 使用子类 PSPDFTextView 切换 UITextView

关于它的帖子,显示修复的内容(带有漂亮的 Gif 动画)在这里:Fixing UITextView on iOS 7

git 在这里:PSPDFTextView

【讨论】:

    猜你喜欢
    • 2015-02-10
    • 1970-01-01
    • 1970-01-01
    • 2012-07-21
    • 1970-01-01
    • 1970-01-01
    • 2013-02-09
    • 1970-01-01
    • 2015-10-18
    相关资源
    最近更新 更多