IOS 7.1 UITextView still doesn't scroll cursor / caret after newline

Since iOS 7, a UITextView does not automatically scroll the cursor, as the user enters text that flows into a new line. This question is well documented on SO and elsewhere. For me, the problem is still present in iOS 7.1. What am I doing wrong?

enter image description here

I installed Xcode 5.1 and configured iOS 7.1. I am using Auto Layout.

This is how I place the contents of the text view above the keyboard:

 - (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; } 

What I tried: I tried a lot of solutions posted to SO on this issue, since it relates to iOS 7. All the solutions I tried do not seem to be well preserved for text representations displaying an attribute string. In the next three steps, I will tell you how the most answer to SO ( https://stackoverflow.com/a/3/9/12 ) answers that the user first pressed the return key.

(1.) The text view became the first responder in viewDidLoad . Scroll to the bottom of the text view where the cursor is.

enter image description here

(2.) Before entering a single character, press the return key on the keyboard. The carriage disappears out of sight.

enter image description here

(3.) Pressing the return key again seems to normalize the situation. (Note: deleting the last new line, however, causes the carriage to disappear again).

enter image description here

+25
ios ios7 uitextview
Mar 11 '14 at 3:16
source share
5 answers

Improved solution code for the descendant UITextView class:

 #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; } 

Please note that this does not work when the Down key is pressed on the Bluetooth keyboard.

+10
Jun 08 '14 at 17:41
source share

A sustainable solution should be delayed in the following situations:

(1.) a text representation displaying an attribute string

(2.) a new line created by pressing the return key on the keyboard

(3.) a new line created by entering text that overflows onto the next line

(4.) copy and paste text

(5.) a new line created by pressing the return key for the first time (see 3 steps in OP)

(6.) device rotation

(7.) In some case, I cannot think that you ...

To meet these requirements in iOS 7.1, it seems that you still need to manually scroll to the carriage.

Typically, to view solutions that manually scroll to the caret when the text view delegate method textViewDidChange: is called . However, I found that this method does not satisfy situation No. 5 above. Even calling layoutIfNeeded before scrolling to the caret did not help. Instead, I had to scroll to the carriage inside the CATransaction termination 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]; } } 

Why does this work? I have no idea. You will have to ask an Apple engineer.

For completeness, here is all the code related to my solution:

 #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 

If you find a situation where this does not work, let me know.

+9
Mar 13 '14 at 5:00
source share

I solved this by getting the actual position of the carriage and adjusting it, here is my method:

 - (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]; } } } 

If you only need to navigate after the user clicks the return / enter button, try:

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

Let me know if this works for you!

+3
Mar 11 '14 at 4:01
source share

I can not find the source source, but it works on 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; } } 
0
Mar 11 '14 at 13:28
source share

Someone made a subclass that solves all UITextView related problems in a UITextView . The implementation couldn't be simpler - switch the UITextView from a subclass of PSPDFTextView .

A post about this showing what is fixed (with nice Gif animations) is here: Fixing a UITextView on iOS 7

git is here: PSPDFTextView

0
Apr 10 '14 at 7:52
source share



All Articles