Scroll to bottom of UITextView erratic in iOS 7
Solution 1
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];
}
Solution 2
This works for me in iOS7.
-(void) scrollToBottom {
[textView scrollRangeToVisible:NSMakeRange([textView.text length], 0)];
[textView setScrollEnabled:NO];
[textView setScrollEnabled:YES];
}
Solution 3
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];
}
Solution 4
Try this:
// Don't forget to set textView's delegate
-(void)textViewDidChangeSelection:(UITextView *)textView {
[textView scrollRangeToVisible:NSMakeRange([textView.text length], 0)];
}
Solution 5
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)
Related videos on Youtube
Comments
-
Mikt25 almost 2 years
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 over 10 yearsFor 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. -
Mikt25 over 10 yearsGood 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.
-
philipkd almost 7 yearsIs this still an issue in iOS 10?
-
Robert Atkins almost 4 yearsIs 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)
-
-
Mikt25 over 10 yearsThis 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 over 10 yearsExcellent 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];
-
tzer about 10 yearsSolved my problem. Thanks.
-
boxed about 10 yearsSolved the problem for me too, thanks. And animates nicely too!
-
Renjith K N about 10 yearshi welcome to so, it will be more helpfull, if you give simple explanations, along with your answer .. cheers & thank you...
-
svarion about 10 yearshi my response is based on this 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 :)
-
Donald Burr about 10 yearsWorks for me as well. Saved me quite a lot of hair-tearing-out tonight. :)
-
Duck almost 10 years+1 ... I don't know why they down voted you because this is the only answer that works for iOS 7. Thanks.
-
Scott Carter over 9 yearsThis 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 :)
-
register over 9 yearsThis 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!!
-
Allen over 9 yearsThis method will block scroll with doing paste action which is obvious when the content height is bigger than the textView height.
-
MitchellTR over 9 yearsThis is still a super obnoxious iOS bug and this is the best workaround I've found anywhere online. Works like a charm.
-
alexgophermix over 9 yearsAmazed that this bug still exists. Reproduced on iOS 8.1 and fixed with this great piece of code.
-
aramusss about 9 yearsThis does not work for me, it scrolls buggy for every line I add to the UITextView
-
Nate almost 9 yearsThis 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. -
Nicolas Miari almost 9 yearsiOS 9 beta 5 here. I can confirm that this solution works.
-
Mert over 8 yearsOn iOS8.1 observed the same thing as Nate. Removing the last two lines solved it for me too.