25

The following code will work fine in iOS < 7.0. In iOS 7 the scrolling will be choppy and erratic while the UITextView is updating. I'm not sure if this is a bug in iOS 7, or I am doing something wrong.

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

@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
zoul
  • 102,279
  • 44
  • 260
  • 354
Mikt25
  • 695
  • 1
  • 9
  • 18
  • For what it’s worth, I’m having problems even with a simple `setContentOffset` call. The content offset changes, but the view doesn’t scroll. The accepted answer worked. – zoul Nov 13 '13 at 16:47
  • Good point zoul. This is why I added both setContentOffset and scrollRageToVisible to show that neither method of scrolling works like it should with the new UITextView in iOS 7. – Mikt25 Nov 13 '13 at 20:46
  • Is this still an issue in iOS 10? – philipkd Jun 12 '17 at 19:07
  • Is this still an issue in iOS 13? (It appears to be, no matter what I do I can't get the damned thing to scroll. *sigh*) – Robert Atkins Aug 11 '20 at 10:48

9 Answers9

56

This works for me in iOS7.

-(void) scrollToBottom {
  [textView scrollRangeToVisible:NSMakeRange([textView.text length], 0)];
  [textView setScrollEnabled:NO];
  [textView setScrollEnabled:YES];
}
Souljacker
  • 774
  • 1
  • 13
  • 35
dklt
  • 1,703
  • 1
  • 12
  • 12
  • Solved the problem for me too, thanks. And animates nicely too! – boxed Apr 20 '14 at 17:29
  • Works for me as well. Saved me quite a lot of hair-tearing-out tonight. :) – Donald Burr May 12 '14 at 04:29
  • This solved my problem also, but I switched the first 2 lines of code so that I set scrollEnabled = NO before calling scrollRangeToVisible. Though both implementations work, my version feels less like a race condition :) – Scott Carter Sep 13 '14 at 03:11
  • This method will block scroll with doing paste action which is obvious when the content height is bigger than the textView height. – Allen Oct 15 '14 at 08:13
  • This worked for me in iOS 7, too, but in iOS 8.?, it stopped working (scrolling was not re-enabled). I just removed the two calls to `setScrollEnabled:` for iOS 8. – Nate Jun 20 '15 at 21:26
  • 3
    iOS 9 beta 5 here. I can confirm that this solution **works**. – Nicolas Miari Aug 14 '15 at 05:07
  • On iOS8.1 observed the same thing as Nate. Removing the last two lines solved it for me too. – Mert Sep 08 '15 at 11:05
28

This is obviously an iOS 7 bug. Here is a workaround until apple fixes it. The workaround is basically instantiates a UITextView by creating an NSTextStorage and NSLayoutManager from scratch. Apple must have forgotten to initialize something in UITextView initialization method. 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];
}
sunkehappy
  • 8,970
  • 5
  • 44
  • 65
RawMean
  • 8,374
  • 6
  • 55
  • 82
  • Excellent work! To get this to work with my OP, I obviously had to change the name to textView. I also had to set some properties such as font to the same and then called [self.view addSubview:textView]; – Mikt25 Oct 13 '13 at 19:59
  • This is really genius!! It takes a lot of intuition to understand that it was a bug in the sdk. By the way it has not been fixed even in 8.0. Thanks for your great contribution!! – register Oct 13 '14 at 17:10
  • 1
    This is _still_ a super obnoxious iOS bug and this is the best workaround I've found anywhere online. Works like a charm. – MitchellTR Nov 13 '14 at 23:27
  • Amazed that this bug still exists. Reproduced on iOS 8.1 and fixed with this great piece of code. – alexgophermix Feb 03 '15 at 23:07
4

There are two problems in iOS 7 that could explain your problem:

  • The contentOffset is not always up to date in iOS 7.
  • scrollRangeToVisible: will not scroll to an empty line at the end of the text view.

The solution could be:

-(void)scrollOutputToBottom {
    CGRect caretRect = [textView caretRectForPosition:textView.endOfDocument];
    [textView scrollRectToVisible:caretRect animated:NO];
}
davidisdk
  • 3,358
  • 23
  • 12
  • This code works fine in iOS 6 but has no effect in iOS 7. BTW: Do you know if contentOffset not being up to date is a bug in iOS 7? – Mikt25 Oct 10 '13 at 16:42
  • +1 ... I don't know why they down voted you because this is the only answer that works for iOS 7. Thanks. – Duck Jun 01 '14 at 12:11
2

Try this:

// Don't forget to set textView's delegate 
-(void)textViewDidChangeSelection:(UITextView *)textView {
    [textView scrollRangeToVisible:NSMakeRange([textView.text length], 0)];
}
Allen
  • 6,745
  • 5
  • 41
  • 59
1

For those who are using Swift, I post here the same answer as RawMean (thanks again!). As I wrote this (dec. 2014), the problem still exist in iOS 8.1 and his solution work perfectly...

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...

let length = countElements(textView.text)
let range:NSRange = NSMakeRange(length - 1, 1)
textView.scrollRangeToVisible(range)
Steve S.
  • 521
  • 9
  • 30
0

Basically setScrollEnabled = YES need to be set before layoutSubviews get called. It worked for me.

Shilpi
  • 498
  • 1
  • 6
  • 13
0

That works for me. Reference: UITextView setText should not jump to top in 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];
}
Community
  • 1
  • 1
leo5th
  • 41
  • 3
0

Swift 2.0 - IOS 8

This is basically a Swift 2.0 version of dklt's answer above. Previously I was using the same method without the 2 lines of 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.

The 2 lines of scrollEnabled doesn't makes much sense, but after adding them the method works consistently!

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...

ONLY dklt's original solution works for me. 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
}
Community
  • 1
  • 1
interceptwind
  • 665
  • 4
  • 14
-1

please try this solution

-(void) scrollToBottom {
    [textView setContentOffset:CGPointMake(0.0, textView.contentSize.height) animated:YES];
}
svarion
  • 1
  • 2
  • 2
    hi welcome to so, it will be more helpfull, if you give simple explanations, along with your answer .. cheers & thank you... – Renjith K N Apr 22 '14 at 08:23
  • 1
    hi my response is based on this http://stackoverflow.com/a/2557893/2870119 ...scrollview content offset is moved to the bottom of the content view (so I'm using its height), I'm using this solution and simply works :) – svarion Apr 22 '14 at 08:37
  • This does not work for me, it scrolls buggy for every line I add to the UITextView – aramusss Mar 21 '15 at 22:39