UITextView的文字超越了界限

时间:2014-02-19 18:51:47

标签: ios objective-c ios7 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.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

更新,仍未解决

7 个答案:

答案 0 :(得分:8)

我认为这是一个更好的答案。每当调用shouldChangeTextInRange委托方法时,我们调用我们的didFit: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;
}

EDIT 好的,如果更改文本格式,还需要添加一些检查。在我的情况下,用户可以更改字体或使其变为粗体,更改段落样式等。所以现在任何这些更改也可能导致文本超出textView边框。

首先,您需要确保使用textViews undoManager注册这些更改。请参阅下面的示例(我只是复制整个attributionString,以便在调用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);
        }
    }
}

答案 1 :(得分:3)

建议不要将

boundingRectWithSize:options:attributes:context:用于textview,因为它不会采用textview的各种属性(例如填充),因此会返回不正确或不精确的值。

要确定textview的文本大小,请使用布局管理器的usedRectForTextContainer:和textview的文本容器来获取文本所需的精确矩形,同时考虑所有必需的布局约束和textview怪癖。

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

我建议在调用processEditingForTextStorage:edited:range:changeInLength:invalidatedRange:实现后,在super中执行此操作。这意味着通过提供自己的文本容器并将其布局管理器设置为子类的实例来替换textview的布局管理器。这样您就可以从用户提出的textview中提交更改,检查rect是否仍然可以接受,如果没有则撤消。

答案 2 :(得分:2)

您需要自己动手。基本上它会像这样工作:

  1. UITextViewDelegate的{​​{1}}方法中查找当前文字的大小(例如,NSString textView:shouldChangeTextInRange:replacementText:)。
  2. 如果大小大于允许,则返回FALSE,否则返回TRUE。
  3. 如果用户输入的内容大于您允许的内容,请向用户提供自己的反馈。
  4. 编辑:由于sizeWithFont:constrainedToSize:已被弃用,请使用sizeWithFont:

    示例:

    boundingRectWithSize:options:attributes:context:

答案 3 :(得分:1)

您可以检查边界矩形的大小,如果它太大,请调用撤消管理器撤消上一个操作。可以是粘贴操作,也可以输入文字或换行符。

这是一个快速入侵,检查文本的高度是否太接近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;
}

答案 4 :(得分:1)

我创建了一个测试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

答案 5 :(得分:1)

IOS 7中有一个新类与UITextviews携手合作,这是NSTextContainer类

它通过Textviews文本容器属性

与UITextview一起使用

它有一个名为size ...

的属性

尺寸 控制接收器边界矩形的大小。默认值:CGSizeZero。

@property(非原子)CGSize大小 讨论 此属性定义从lineFragmentRectForProposedRect返回的布局区域的最大大小:atIndex:writingDirection:remainingRect:。值0.0或更小意味着没有限制。

我仍然在理解它并尝试它,但我相信它应该可以解决你的问题。

答案 6 :(得分:1)

无需查找行数。 我们可以通过从textview计算光标位置来获得所有这些东西,并根据我们可以根据UITextView的高度最小化UITextView的UIFont。

以下是链接。请参阅此内容。 https://github.com/jayaprada-behera/CustomTextView