UITextView в iOS7 обрезает последнюю строку текстовой строки

UITextView в iOS7 был действительно странным. Когда вы печатаете и вводите последнюю строку вашего UITextView, представление прокрутки не прокручивается до нижнего предела, как это должно быть, и оно приводит к "обрезанию" текста. Я попытался установить для его свойства clipsToBound значение NO, но оно все равно обрезает текст.

Я не хочу вызывать "setContentOffset: animated", потому что для одного: это очень хакерское решение... во-вторых: если курсор был посередине (вертикально) нашего текстового представления, это вызовет нежелательную прокрутку.

Вот скриншот.

Любая помощь будет принята с благодарностью!

Спасибо!

11 ответов

Решение

Проблема связана с iOS 7. В текстовом представлении делегата добавьте этот код:

- (void)textViewDidChange:(UITextView *)textView {
    CGRect line = [textView caretRectForPosition:
        textView.selectedTextRange.start];
    CGFloat overflow = line.origin.y + line.size.height
        - ( textView.contentOffset.y + textView.bounds.size.height
        - textView.contentInset.bottom - textView.contentInset.top );
    if ( overflow > 0 ) {
        // We are at the bottom of the visible text and introduced a line feed, scroll down (iOS 7 does not do it)
        // Scroll caret to visible area
        CGPoint offset = textView.contentOffset;
        offset.y += overflow + 7; // leave 7 pixels margin
        // Cannot animate with setContentOffset:animated: or caret will not appear
        [UIView animateWithDuration:.2 animations:^{
            [textView setContentOffset:offset];
        }];
    }
}

Решение, которое я нашел здесь, было добавить исправление в одну строку после создания UITextView:

self.textview.layoutManager.allowsNonContiguousLayout = NO;

Эта строка исправила три проблемы, возникшие у меня при создании редактора кода на основе UITextView с подсветкой синтаксиса на iOS7:

  1. Прокрутка, чтобы держать текст в поле зрения при редактировании (выпуск этого поста)
  2. UITextView иногда прыгает вокруг после того, как распустить клавиатуру
  3. UITextView случайные переходы прокрутки при попытке прокрутки представления

Обратите внимание, я изменил размер всего UITextView, когда клавиатура отображается / скрывается.

Попробуйте реализовать -textViewDidChangeSelection: метод делегата от UITextViewDelegate как это:

-(void)textViewDidChangeSelection:(UITextView *)textView {
    [textView scrollRangeToVisible:textView.selectedRange];
}

Вот модифицированная версия выбранного ответа от davidisdk.

- (void)textViewDidChange:(UITextView *)textView {
    NSRange selection = textView.selectedRange;

    if (selection.location + selection.length == [textView.text length]) {
        CGRect caretRect = [textView caretRectForPosition:textView.selectedTextRange.start];
        CGFloat overflow = caretRect.origin.y + caretRect.size.height - (textView.contentOffset.y + textView.bounds.size.height - textView.contentInset.bottom - textView.contentInset.top);

        if (overflow > 0.0f) {
            CGPoint offset = textView.contentOffset;
            offset.y += overflow + 7.0f;

            [UIView animateWithDuration:0.2f animations:^{
                [textView setContentOffset:offset];
            }];
        }
    } else {
        [textView scrollRangeToVisible:selection];
    }
}

Я получаю сообщение об ошибке, что, когда размер содержимого textView больше, чем границы и курсор находится за пределами экрана (например, с помощью клавиатуры и нажатия клавиши со стрелкой), textView не будет анимироваться для вставляемого текста.

Имхо, это окончательный ответ на все типичные проблемы UITextView, связанные с прокруткой / клавиатурой в iOS 7. Он чистый, его легко читать, легко использовать, легко обслуживать и легко использовать повторно.

Основная хитрость:просто измените размер UITextView, а не вставку содержимого!

Вот практический пример. Само собой разумеется, что у вас есть UIViewController на основе NIB/Storyboard, использующий Autolayout, а UITextView заполняет весь корневой вид в UIViewController. Если нет, вам придется адаптировать способ изменения textViewBottomSpaceConstraint к вашим потребностям.

Как:


Добавьте эти свойства:

@property (nonatomic, weak) IBOutlet NSLayoutConstraint *textViewBottomSpaceConstraint;
@property (nonatomic) CGFloat textViewBottomSpaceConstraintFromNIB;

Подключите textViewBottomSpaceConstraint в Интерфейсном Разработчике (не забывайте!)

Затем в viewDidLoad:

// Save the state of the UITextView's bottom constraint as set up in your NIB/Storyboard
self.textViewBottomSpaceConstraintFromNIB = self.textViewBottomSpaceConstraint.constant;

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(keyboardWillShowNotification:)
                                             name:UIKeyboardWillShowNotification
                                           object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(keyboardWillHideNotification:)
                                             name:UIKeyboardWillHideNotification
                                           object:nil];

Добавьте эти методы для обработки изменения размера клавиатуры (спасибо https://github.com/brennanMKE/Interfaces/tree/master/Keyboarding - эти методы предоставлены brennan!):

- (void)keyboardWillShowNotification:(NSNotification *)notification {
    CGFloat height = [self getKeyboardHeight:notification forBeginning:TRUE];
    NSTimeInterval duration = [self getDuration:notification];
    UIViewAnimationOptions curve = [self getAnimationCurve:notification];

    [self keyboardWillShowWithHeight:height duration:duration curve:curve];
}

- (void)keyboardWillHideNotification:(NSNotification *)notification {
    CGFloat height = [self getKeyboardHeight:notification forBeginning:FALSE];
    NSTimeInterval duration = [self getDuration:notification];
    UIViewAnimationOptions curve = [self getAnimationCurve:notification];

    [self keyboardWillHideWithHeight:height duration:duration curve:curve];
}

- (NSTimeInterval)getDuration:(NSNotification *)notification {
    NSDictionary *info = [notification userInfo];

    NSTimeInterval duration;

    NSValue *durationValue = [info objectForKey:UIKeyboardAnimationDurationUserInfoKey];
    [durationValue getValue:&duration];

    return duration;
}

- (CGFloat)getKeyboardHeight:(NSNotification *)notification forBeginning:(BOOL)forBeginning {
    NSDictionary *info = [notification userInfo];

    CGFloat keyboardHeight;

    NSValue *boundsValue = nil;
    if (forBeginning) {
        boundsValue = [info valueForKey:UIKeyboardFrameBeginUserInfoKey];
    }
    else {
        boundsValue = [info valueForKey:UIKeyboardFrameEndUserInfoKey];
    }

    UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
    if (UIDeviceOrientationIsLandscape(orientation)) {
        keyboardHeight = [boundsValue CGRectValue].size.width;
    }
    else {
        keyboardHeight = [boundsValue CGRectValue].size.height;
    }

    return keyboardHeight;
}

- (UIViewAnimationOptions)getAnimationCurve:(NSNotification *)notification {
    UIViewAnimationCurve curve = [[notification.userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue];

    switch (curve) {
        case UIViewAnimationCurveEaseInOut:
            return UIViewAnimationOptionCurveEaseInOut;
            break;
        case UIViewAnimationCurveEaseIn:
            return UIViewAnimationOptionCurveEaseIn;
            break;
        case UIViewAnimationCurveEaseOut:
            return UIViewAnimationOptionCurveEaseOut;
            break;
        case UIViewAnimationCurveLinear:
            return UIViewAnimationOptionCurveLinear;
            break;
    }

    return kNilOptions;
}

Наконец, добавьте эти методы для реагирования на уведомления клавиатуры и измените размер UITextView.

- (void)keyboardWillShowWithHeight:(CGFloat)height duration:(CGFloat)duration curve:(UIViewAnimationOptions)curve
{
    CGFloat correctionMargin = 15; // you can experiment with this margin so the bottom text view line is not flush against the keyboard which doesn't look nice
    self.textViewBottomSpaceConstraint.constant = height + correctionMargin;

    [self.view setNeedsUpdateConstraints];

    [UIView animateWithDuration:duration delay:0 options:curve animations:^{
        [self.view layoutIfNeeded];
    } completion:^(BOOL finished) {

    }];
}

- (void)keyboardWillHideWithHeight:(CGFloat)height duration:(CGFloat)duration curve:(UIViewAnimationOptions)curve
{
    self.textViewBottomSpaceConstraint.constant = self.textViewBottomSpaceConstraintFromNIB;

    [self.view setNeedsUpdateConstraints];

    [UIView animateWithDuration:duration delay:0 options:curve animations:^{
        [self.view layoutIfNeeded];
    } completion:^(BOOL finished) {

    }];
}

Также добавьте эти методы, чтобы автоматически прокрутить туда, где пользователь нажал

- (void)textViewDidBeginEditing:(UITextView *)textView
{
    [textView scrollRangeToVisible:textView.selectedRange];
}

- (void)textViewDidChangeSelection:(UITextView *)textView
{
    [textView scrollRangeToVisible:textView.selectedRange];
}
textView.contentInset = UIEdgeInsetsMake(0.0, 0.0, 10.0, 0.0);

Это также решит вашу проблему

Эта строка приводит к тому, что последняя строка текста не отображается для меня:

textView.scrollEnabled = false

Попробуйте удалить это и посмотреть, что произойдет...

Вот версия MonoTouch самого прекрасного решения Давидидска (сверху).

TextView.SelectionChanged += (object sender, EventArgs e) => {
                TextView.ScrollRangeToVisible(TextView.SelectedRange);
            };


            TextView.Changed += (object sender, EventArgs e) => {

                CGRect line = TextView.GetCaretRectForPosition(TextView.SelectedTextRange.Start);
                nfloat overflow = line.Y + line.Height - 
                                     (TextView.ContentOffset.Y + 
                                      TextView.Bounds.Height -          
                                      TextView.ContentInset.Bottom -
                                      TextView.ContentInset.Top );
                if ( overflow > 0 ) 
                {
                    // We are at the bottom of the visible text and introduced 
                    // a line feed, scroll down (iOS 7 does not do it)
                    // Scroll caret to visible area
                    CGPoint offset = TextView.ContentOffset;
                    offset.Y+= overflow + 7; // leave 7 pixels margin
                    // Cannot animate with setContentOffset:animated: 
                    // or caret will not appear
                    UIView.Animate(0.1,()=> {
                        TextView.ContentOffset = offset;
                    });
                }
            };

Если вы используете StoryBoard, то такое поведение также может произойти, если вы оставили AutoLayout включенным (как оно есть по умолчанию) и не установили ограничения сверху / снизу для вашего UITextView. Проверьте инспектора файлов, чтобы увидеть, каков ваш статус AutoLayout...

   textView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

Это решило проблему для меня

Установите theViewDelegate на "self" в вашем.m и используйте в своем.h, затем добавьте этот код в свой.m

Будет обрабатывать ОБА версии этого сбоя, возникающие при переходе к следующей строке с текстом (перенос или возврат каретки) и вводу... И переходу на следующую строку только с возвратом каретки и без ввода (этот код, в отличие от других код, будет прокручивать, чтобы показать, что мигающий курсор не обрезается в этом втором сценарии сбоя)

//!!!*!!****!*!**!*!*!!!MAKE SURE YOU SET DELEGATE AND USE THE <UITEXTVIEWDELEGATE>

-(void)textViewDidChange:(UITextView *)textView {
    [theTextView scrollRangeToVisible:[textView selectedRange]];//resizing textView frame causes text itself "content frame?" to still go below the textview frame and get clipped... auto-scrolling must be implimented. (iOS7 bug)
}

-(BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
    if (([text isEqualToString:@"\n"]) && (range.location == textView.text.length)) {//"return" at end of textView
        [textView scrollRectToVisible:CGRectMake(5,5,5,999999999999999999) animated:NO];//for some reason the textViewDidChange auto scrolling doesnt work with a carriage return at the end of your textView... so I manually set it INSANELY low (NOT ANIMATED) here so that it automagically bounces back to the proper position before interface refreshes when textViewDidChange is called after this.
    }
    return YES;
}
Другие вопросы по тегам