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

Текст UITextView выходит за рамки

У меня есть прокручиваемый UITextView с его layoutManager maximumNumberOfLines, установленным в 9, который отлично работает, но я не могу найти метод в NSLayoutManager, который ограничивает текст не выходящим за рамки UITextView.

Возьмем, к примеру, на этом скриншоте, курсор находится на 9-й строке (первая строка обрезана в верхней части экрана, поэтому не обращайте на это внимания). Если пользователь продолжает вводить новые символы, пробелы или нажать клавишу возврата, курсор продолжает выходить из экрана, а строка UITextView продолжает увеличиваться.

enter image description here

Я не хочу ограничивать количество символов UITextView из-за того, что инородные символы имеют разные размеры.

Я пытался исправить это несколько недель; Я бы очень признателен за любую помощь.

CustomTextView.h

#import <UIKit/UIKit.h>

@interface CustomTextView : UITextView <NSLayoutManagerDelegate>

@end

CustomTextView.m

#import "CustomTextView.h"

@implementation CustomTextView

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self)
    {
        self.backgroundColor = [UIColor clearColor];
        self.font = [UIFont systemFontOfSize:21.0];
        self.dataDetectorTypes = UIDataDetectorTypeAll;
        self.layoutManager.delegate = self;
        self.tintColor = [UIColor companyBlue];
        [self setLinkTextAttributes:@{NSForegroundColorAttributeName:[UIColor companyBlue]}];
        self.scrollEnabled = NO;
        self.textContainerInset = UIEdgeInsetsMake(8.5, 0, 0, 0);
        self.textContainer.maximumNumberOfLines = 9;
    }
    return self;
}

- (CGFloat)layoutManager:(NSLayoutManager *)layoutManager lineSpacingAfterGlyphAtIndex:(NSUInteger)glyphIndex withProposedLineFragmentRect:(CGRect)rect
{
    return 4.9;
}

@end

Обновление, все еще не разрешено

4b9b3361

Ответ 1

Вот лучший ответ, который я думаю. Всякий раз, когда вызывается метод делегата shouldChangeTextInRange, мы вызываем нашу функцию doFit: string: range, чтобы увидеть, превышает ли результирующая высота текста высоту представления. Если это так, мы возвращаем NO, чтобы предотвратить изменение.

-(BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
    FLOG(@" called");

    // allow deletes
    if (text.length == 0)
        return YES;

    // Check if the text exceeds the size of the UITextView
    return [self doesFit:textView string:text range:range];

}
- (float)doesFit:(UITextView*)textView string:(NSString *)myString range:(NSRange) range;
{
    // Get the textView frame
    float viewHeight = textView.frame.size.height;
    float width = textView.textContainer.size.width;

    NSMutableAttributedString *atrs = [[NSMutableAttributedString alloc] initWithAttributedString: textView.textStorage];
    [atrs replaceCharactersInRange:range withString:myString];

    NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:atrs];
    NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize: CGSizeMake(width, FLT_MAX)];
    NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];

    [layoutManager addTextContainer:textContainer];
    [textStorage addLayoutManager:layoutManager];
    float textHeight = [layoutManager
            usedRectForTextContainer:textContainer].size.height;
    FLOG(@" viewHeight = %f", viewHeight);
    FLOG(@" textHeight = %f", textHeight);

    if (textHeight >= viewHeight - 1) {
        FLOG(@" textHeight >= viewHeight - 1");
        return NO;
    } else
        return YES;
}

ИЗМЕНИТЬ OK, вам также нужно будет добавить некоторые проверки, если вы измените формат текста. В моем случае пользователь может изменить шрифт или сделать его полужирным, изменить стиль абзаца и т.д. Итак, теперь любое из этих изменений также может привести к тому, что текст будет превышать границы textView.

Итак, сначала вам нужно убедиться, что вы регистрируете эти изменения с помощью textViews undoManager. Ниже приведен пример (я просто копирую всю атрибутированную строку, чтобы я мог вернуть ее, если вызывается undo).

// This is in my UITextView subclass but could be anywhere

// This gets called to undo any formatting changes 
- (void)setMyAttributedString:(NSAttributedString*) atstr {
    self.attributedText = atstr;
    self.selectedRange = _undoSelection;
}
// Before we make any format changes save the attributed string with undoManager
// Also save the current selection (maybe should save this with undoManager as well using a custom object containing selection and attributedString)
- (void)formatText:(id)sender {
    //LOG(@"formatText: called");
    NSAttributedString *atstr = [[NSAttributedString alloc] initWithAttributedString:self.textStorage];
    [[self undoManager] registerUndoWithTarget:self
                               selector:@selector(setMyAttributedString:)
                                 object:atstr];
    // Remember selection
    _undoSelection = self.selectedRange;

   // Add text formatting attributes
   ...
   // Now tell the delegate that something changed
   [self.delegate textViewDidChange:self];
}

Теперь проверьте размер делегата и отмените его, если он не подходит.

-(void)textViewDidChange:(UITextView *)textView {
    FLOG(@" called");
    if ([self isTooBig:textView]) {
        FLOG(@" text is too big so undo it!");
        @try {
            [[textView undoManager] undo];
        }
        @catch (NSException *exception) {
            FLOG(@" exception undoing things %@", exception);
        }
    }
}

Ответ 2

boundingRectWithSize:options:attributes:context: не рекомендуется для текстовых просмотров, потому что он не принимает различные атрибуты textview (например, отступы) и, таким образом, возвращает неправильное или неточное значение.

Чтобы определить размер текстового текста, используйте диспетчер компоновки usedRectForTextContainer: с текстовым контейнером textview, чтобы получить точный прямоугольник, необходимый для текста, с учетом всех необходимых ограничений макета и особенностей текста.

CGRect rect = [self.textView.layoutManager usedRectForTextContainer:self.textView.textContainer];

Я бы рекомендовал сделать это в processEditingForTextStorage:edited:range:changeInLength:invalidatedRange: после вызова реализации super. Это означало бы замену менеджера компоновки textview, предоставив свой собственный текстовый контейнер и установив его диспетчер макетов в экземпляр вашего подкласса. Таким образом, вы можете зафиксировать изменения в текстовом представлении, сделанные пользователем, проверить правильность выбора и отменить, если нет.

Ответ 3

Вам нужно будет сделать это самостоятельно. В основном это будет работать следующим образом:

  • В вашем методе UITextViewDelegate textView:shouldChangeTextInRange:replacementText: найдите размер вашего текущего текста (например, NSString sizeWithFont:constrainedToSize:).
  • Если размер больше, чем вы разрешите возврат FALSE, в противном случае верните TRUE.
  • Предоставьте свои собственные отзывы пользователю, если они напечатают что-то большее, чем вы разрешаете.

EDIT: поскольку sizeWithFont: устарел, используйте boundingRectWithSize:options:attributes:context:

Пример:

NSString *string = @"Hello World"; 

UIFont *font = [UIFont fontWithName:@"Helvetica-BoldOblique" size:21];

CGSize constraint = CGSizeMake(300,NSUIntegerMax);

NSDictionary *attributes = @{NSFontAttributeName: font};

CGRect rect = [string boundingRectWithSize:constraint 
                                   options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)  
                                attributes:attributes 
                                   context:nil];

Ответ 4

Вы можете проверить размер ограничивающего прямоугольника, и если он слишком большой, вызовите диспетчер отмены, чтобы отменить последнее действие. Может быть операция вставки или ввести текст или новый символ строки.

Вот быстрый хак, который проверяет, слишком ли высока текст слишком высока для textView. Также проверяет, что textView rect содержит текст rect. Возможно, вам придется поиграть с этим еще в соответствии с вашими потребностями.

-(void)textViewDidChange:(UITextView *)textView {
    if ([self isTooBig:textView]) {
        FLOG(@" too big so undo");
        [[textView undoManager] undo];
    }
}
/** Checks if the frame of the selection is bigger than the frame of the textView
 */
- (bool)isTooBig:(UITextView *)textView {
    FLOG(@" called");

    // Get the rect for the full range
    CGRect rect = [textView.layoutManager usedRectForTextContainer:textView.textContainer];

    // Now convert to textView coordinates
    CGRect rectRange = [textView convertRect:rect fromView:textView.textInputView];
    // Now convert to contentView coordinates
    CGRect rectText = [self.contentView convertRect:rectRange fromView:textView];

    // Get the textView frame
    CGRect rectTextView = textView.frame;

    // Check the height
    if (rectText.size.height > rectTextView.size.height - 16) {
        FLOG(@" rectText height too close to rectTextView");
        return YES;
    }

    // Find the intersection of the two (in the same coordinate space)
    if (CGRectContainsRect(rectTextView, rectText)) {
        FLOG(@" rectTextView contains rectText");
        return NO;
    } else
        return YES;
}

ДРУГОЙ ВАРИАНТ - здесь мы проверяем размер, и если он слишком велик, чтобы вводить какие-либо новые символы, кроме случаев его удаления. Не очень красиво, поскольку это также предотвращает заполнение строки вверху, если высота превышена.

bool _isFull;

-(BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
    FLOG(@" called");

    // allow deletes
    if (text.length == 0)
        return YES;

    // Check if the text exceeds the size of the UITextView
    if (_isFull) {
        return NO;
    }

    return YES;
}
-(void)textViewDidChange:(UITextView *)textView {
    FLOG(@" called");
    if ([self isTooBig:textView]) {
        FLOG(@" text is too big!");
        _isFull = YES;
    } else {
        FLOG(@" text is not too big!");
        _isFull = NO;
    }
}

/** Checks if the frame of the selection is bigger than the frame of the textView
 */
- (bool)isTooBig:(UITextView *)textView {
    FLOG(@" called");

    // Get the rect for the full range
    CGRect rect = [textView.layoutManager usedRectForTextContainer:textView.textContainer];

    // Now convert to textView coordinates
    CGRect rectRange = [textView convertRect:rect fromView:textView.textInputView];
    // Now convert to contentView coordinates
    CGRect rectText = [self.contentView convertRect:rectRange fromView:textView];

    // Get the textView frame
    CGRect rectTextView = textView.frame;

    // Check the height
    if (rectText.size.height >= rectTextView.size.height - 10) {
        return YES;
    }

    // Find the intersection of the two (in the same coordinate space)
    if (CGRectContainsRect(rectTextView, rectText)) {
        return NO;
    } else
        return YES;
}

Ответ 5

Я создал тестовый VC. Он увеличивает счетчик строк каждый раз, когда новая линия достигается в UITextView. Насколько я понимаю, вы хотите ограничить ввод текста не более чем на 9 строк. Надеюсь, это ответит на ваш вопрос.

#import "ViewController.h"

@interface ViewController ()

@property IBOutlet UITextView *myTextView;

@property CGRect previousRect;
@property int lineCounter;

@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];

[self.myTextView setDelegate:self];

self.previousRect = CGRectZero;
self.lineCounter = 0;
}

- (void)textViewDidChange:(UITextView *)textView {
UITextPosition* position = textView.endOfDocument;

CGRect currentRect = [textView caretRectForPosition:position];

if (currentRect.origin.y > self.previousRect.origin.y){
    self.lineCounter++;
    if(self.lineCounter > 9) {
        NSLog(@"Reached line 10");
        // do whatever you need to here...
    }
}
self.previousRect = currentRect;

}

@end

Ответ 6

В IOS 7 есть новый класс, который работает рука об руку с UITextviews, который является классом NSTextContainer

Он работает с UITextview через свойство текстового контейнера Textviews

у него есть это свойство, называемое размером...

размер Управляет размером ограничивающего прямоугольника приемников. Значение по умолчанию: CGSizeZero.

@property (неатомный) Размер CGSize обсуждение Это свойство определяет максимальный размер области макета, возвращаемой из строкиFragmentRectForProposedRect: atIndex: writingDirection: ОстальноеRect:. Значение 0.0 или меньше означает отсутствие ограничений.

Я все еще в процессе понимания и проверки, но я считаю, что он должен решить вашу проблему.

Ответ 7

Не нужно найти количество строк. Мы можем получить все эти вещи, вычислив позицию курсора из текстового поля, и в соответствии с этим мы сможем минимизировать UIFont UITextView в соответствии с высотой UITextView.

Ниже приведена ссылка. Пожалуйста, обратитесь к этому. https://github.com/jayaprada-behera/CustomTextView