IOS 7.1 UITextView все еще не прокручивает курсор/каретку после новой строки
Так как iOS 7, a UITextView
не прокручивает автоматически курсор, так как пользователь вводит текст, который перетекает в новую строку. Этот вопрос хорошо документирован на SO и в других местах. Для меня проблема все еще присутствует в iOS 7.1. Что я делаю неправильно?
![enter image description here]()
Я установил Xcode 5.1 и настроил iOS 7.1. Я использую Auto Layout.
Вот как я размещаю содержимое текстового представления над клавиатурой:
- (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. Все решения, которые я пробовал, делают не, похоже, хорошо сохраняются для текстовых представлений, отображающих атрибутированную строку. В следующих трех шагах я расскажу, как самый ответный ответ на SO (qaru.site/info/102960/...) отвечает на то, что пользователь впервые нажал на клавишу возврата.
(1.) Текстовое представление стало первым ответчиком в viewDidLoad
. Прокрутите до нижней части текстового вида, где находится курсор.
![enter image description here]()
(2.) Прежде чем вводить один символ, нажмите клавишу возврата на клавиатуре. Карет исчезает с глаз долой.
![enter image description here]()
(3.) Повторное нажатие клавиши возврата, похоже, нормализует ситуацию. (Примечание: удаление последней новой строки, однако, заставляет каретку снова исчезать).
![enter image description here]()
Ответы
Ответ 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;
}
Обратите внимание, что это не работает, когда нажата клавиша "Вниз" на клавиатуре Bluetooth.
Ответ 2
Устойчивое решение должно задерживаться в следующих ситуациях:
(1.) текстовое представление, отображающее атрибутную строку
(2.) новая строка, созданная нажатием клавиши возврата на клавиатуре
(3.) новая строка, созданная путем ввода текста, который переполняется на следующую строку
(4.) скопировать и вставить текст
(5.) новая строка, созданная нажатием клавиши возврата в первый раз (см. 3 шага в OP)
(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];
}
}
Почему это работает? Понятия не имею. Вам придется спросить инженера Apple.
Для полноты здесь весь код, связанный с моим решением:
#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
Если вы обнаружите ситуацию, когда это не работает, сообщите мне.
Ответ 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];
}
}
Сообщите мне, если это сработает для вас!
Ответ 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
Кто-то сделал подкласс, который решает все прокрутки0 связанных проблем в UITextView
. Реализация не может быть проще - переключите UITextView
с подкласса PSPDFTextView
.
Сообщение об этом, показывающее, что фиксировано (с приятными анимациями Gif), находится здесь: Фиксация UITextView на iOS 7
git находится здесь: PSPDFTextView