Подтвердить что ты не робот

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

4b9b3361

Ответ 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