简体   繁体   English

在iOS 7中滚动到UITextView的底部不稳定

[英]Scroll to bottom of UITextView erratic in iOS 7

The following code will work fine in iOS < 7.0. 以下代码在iOS <7.0中可以正常工作。 In iOS 7 the scrolling will be choppy and erratic while the UITextView is updating. 在iOS 7中,当UITextView正在更新时,滚动将变得不稳定且不稳定。 I'm not sure if this is a bug in iOS 7, or I am doing something wrong. 我不确定这是否是iOS 7中的错误,或者我做错了什么。

TestController.h TestController.h

//TODO: Add UITextView in storyboard and tie to textView outlet

#define MAX_TEXT_VIEW_CHARACTERS 1000
@interface TestController : UIViewController  {
    NSMutableString *_outputText;
    NSTimer *_outputTimer;
}

@property (strong, nonatomic) IBOutlet UITextView *textView;

@end

TestController.m TestController.m

@implementation TestController
@synthesize textView;

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    _outputText = [NSMutableString stringWithCapacity:MAX_TEXT_VIEW_CHARACTERS];
    _outputTimer =  [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(outputLine:) userInfo:nil repeats:YES];
}

-(void)outputLine:(NSTimer *) theTimer {
    static int i = 0;
    //Run this 100 times
    if (i > 99) {
        [_outputTimer invalidate];
        return;
    }
    [self outputToScreen:[NSString stringWithFormat:@"Some string %d\r", ++i]];
}

-(void)outputToScreen:(NSString *)str {
    if (!str || !str.length) return;  //Nothing to output

    NSInteger outputTextSize = _outputText.length;
    [_outputText appendString:str];
    if (outputTextSize > MAX_TEXT_VIEW_CHARACTERS)
        [_outputText deleteCharactersInRange:NSMakeRange(0, outputTextSize - MAX_TEXT_VIEW_CHARACTERS)];
    self.textView.text = _outputText;

    [self scrollOutputToBottom];
}

-(void)scrollOutputToBottom {
    CGPoint p = [textView contentOffset];
    [textView setContentOffset:p animated:NO];
    [textView scrollRangeToVisible:NSMakeRange([textView.text length], 0)];
}

@end

This works for me in iOS7. 这适用于iOS7。

-(void) scrollToBottom {
  [textView scrollRangeToVisible:NSMakeRange([textView.text length], 0)];
  [textView setScrollEnabled:NO];
  [textView setScrollEnabled:YES];
}

This is obviously an iOS 7 bug. 这显然是iOS 7的错误。 Here is a workaround until apple fixes it. 这是一个解决方法,直到苹果修复它。 The workaround is basically instantiates a UITextView by creating an NSTextStorage and NSLayoutManager from scratch. 解决方法基本上是通过从头创建NSTextStorageNSLayoutManager来实例化UITextView Apple must have forgotten to initialize something in UITextView initialization method. Apple必须忘记在UITextView初始化方法中初始化一些东西。 I filed a bug report and I hope you do too. 我提交了一份错误报告,我希望你也这样做。

// ios7 bug fix
// check if the device is running iOS 7.0 or later
NSString *reqSysVer = @"7.0";
NSString *currSysVer = [[UIDevice currentDevice] systemVersion];
BOOL osVersionSupported = ([currSysVer compare:reqSysVer  options:NSNumericSearch] != NSOrderedAscending);

if (osVersionSupported) {
    NSTextStorage* textStorage = [[NSTextStorage alloc] init];
    NSLayoutManager* layoutManager = [NSLayoutManager new];
    [textStorage addLayoutManager:layoutManager];
    NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:self.view.bounds.size];
    [layoutManager addTextContainer:textContainer];
    yourTextView = [[UITextView alloc] initWithFrame:someFrameForYourTextView
                                       textContainer:textContainer];
    // if using ARC, remove these 3 lines
    [textContainer release];
    [layoutManager release];
    [textStorage release];
}
else {
    yourTextView = [[UITextView alloc] initWithFrame:someFrameForYourTextView];
}

There are two problems in iOS 7 that could explain your problem: iOS 7中有两个问题可以解释您的问题:

  • The contentOffset is not always up to date in iOS 7. iOS 7中的contentOffset并不总是最新的。
  • scrollRangeToVisible: will not scroll to an empty line at the end of the text view. scrollRangeToVisible:不会滚动到文本视图末尾的空行。

The solution could be: 解决方案可能是:

-(void)scrollOutputToBottom {
    CGRect caretRect = [textView caretRectForPosition:textView.endOfDocument];
    [textView scrollRectToVisible:caretRect animated:NO];
}

Try this: 尝试这个:

// Don't forget to set textView's delegate 
-(void)textViewDidChangeSelection:(UITextView *)textView {
    [textView scrollRangeToVisible:NSMakeRange([textView.text length], 0)];
}

For those who are using Swift, I post here the same answer as RawMean (thanks again!). 对于那些使用Swift的人,我在这里发布与RawMean相同的答案(再次感谢!)。 As I wrote this (dec. 2014), the problem still exist in iOS 8.1 and his solution work perfectly... 正如我写的那样(2014年12月),问题仍然存在于iOS 8.1中,他的解决方案完美地工作......

var textView: UITextView!
var textStorage: NSTextStorage!
var layoutManager: NSLayoutManager!
var textContainer: NSTextContainer!


override func viewDidLoad() {
    textStorage = NSTextStorage()
    layoutManager = NSLayoutManager()
    textStorage.addLayoutManager(layoutManager)

    let newTextViewRect = view.bounds
    let containerSize = CGSize(width: newTextViewRect.width, height: CGFloat.max)

    textContainer = NSTextContainer(size: containerSize)
    layoutManager.addTextContainer(textContainer)

    textView = UITextView(frame: newTextViewRect, textContainer: textContainer)

    textView.delegate = self
    view.addSubview(textView)

}

override func viewDidLayoutSubviews() {
    textView.frame = view.bounds
}

and I used the scrollRangeToVisible method to scroll smoothly at the bottom as text is added... 我使用scrollRangeToVisible方法在文本添加时在底部平滑滚动...

let length = countElements(textView.text)
let range:NSRange = NSMakeRange(length - 1, 1)
textView.scrollRangeToVisible(range)

Basically setScrollEnabled = YES need to be set before layoutSubviews get called. 基本上setScrollEnabled = YES需要在调用layoutSubviews之前设置。 It worked for me. 它对我有用。

That works for me. 这对我行得通。 Reference: UITextView setText should not jump to top in ios8 参考: UITextView setText不应该跳转到ios8的顶部

self.textView.layoutManager.allowsNonContiguousLayout = NO;
self.textView.text = fileContent;
if(fileContent.length > 1)
{
    NSRange range = NSMakeRange(self.textView.text.length - 1, 1);
    [self.textView scrollRangeToVisible:range];
}

Swift 2.0 - IOS 8 Swift 2.0 - IOS 8

This is basically a Swift 2.0 version of dklt's answer above. 这基本上是上面dklt的答案的Swift 2.0版本。 Previously I was using the same method without the 2 lines of scrollEnabled . 以前我使用相同的方法没有2行scrollEnabled Most of the time it works fine. 大多数时候它工作正常。 However, when scrollToBottom() is called in quick succession at almost the same time, it doesn't works sometimes. 但是,当几乎同时快速连续调用scrollToBottom()时,它有时不起作用。

The 2 lines of scrollEnabled doesn't makes much sense, but after adding them the method works consistently ! scrollEnabled的两行没有多大意义,但在添加它们之后,该方法始终如一

Note: I have tried to put the 2 lines of scrollEnabled in various position before or after the scrollRangeTovisible , as was suggested in dklt's answer's comments... 注:我试图把2线scrollEnabled在不同位置的前面或后面scrollRangeTovisible ,如dklt的答复的意见建议......

ONLY dklt's original solution works for me. 只有dklt的原始解决方案适合我。 The rest doesn't. 其余的没有。

func scrollToBottom()
{
    let range:NSRange = NSMakeRange(self.textView.text.characters.count - 1, 1)

    self.textView.scrollRangeToVisible(range)
    self.textView.scrollEnabled = false
    self.textView.scrollEnabled = true
}

please try this solution 请试试这个解决方案

-(void) scrollToBottom {
    [textView setContentOffset:CGPointMake(0.0, textView.contentSize.height) animated:YES];
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM