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

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

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

enter image description here

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

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

CustomTextView.h

#import <UIKit/UIKit.h>

@interface CustomTextView : UITextView <NSLayoutManagerDelegate>

@end

CustomTextView.м

#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

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

7 ответов


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

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

изменить ОК, вы также должны добавить некоторые проверки, если вы измените формат текста. В моем случае пользователь может изменить шрифт или сделать его жирным, изменить стиль абзаца и т. д.. Так что теперь любой из эти изменения также могут привести к превышению границ textView.

поэтому сначала вам нужно убедиться, что вы регистрируете эти изменения в textviews undoManager. См. ниже пример (я просто копирую всю attributedString, чтобы я мог вернуть ее, если вызывается 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);
        }
    }
}

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

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

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

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


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

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

EDIT: Since 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];

Я создал тестовый 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

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

вот быстрый взлом, который проверяет, слишком ли высота текста близка к высоте 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;
}

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

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

Это свойство называется size ...

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

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

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


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

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