UITextView的文本超越界限

我有一个不可滚动的UITextView与它的layoutManager maximumNumberOfLines设置为9,这工作正常,但是,我似乎无法在NSLayoutManager中find一个方法限制文本不超过UITextView的框架。

以截图为例,光标位于第9行(第一行被截图在截图顶部,所以忽略)。 如果用户继续input新的字符,空格,或者按回车键,光标会继续离开屏幕,并且UITextView的string会持续变长。

在这里输入图像说明

我不想限制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 

更新,仍然没有解决

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

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

所以首先你需要确保你使用textViews的undoManager来注册这些改变。 看下面的例子(我只是复制整个归属string,所以我可以把它撤回,如果撤消称为)。

 // 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:不build议使用textviews,因为它不会采用textview的各种属性(如填充),因此会返回不正确或不精确的值。

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

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

我build议在processEditingForTextStorage:edited:range:changeInLength:invalidatedRange:调用super实现之后执行此操作。 这将意味着通过提供自己的文本容器并将其布局pipe理器设置为您的子类的实例来replacetextview的布局pipe理器。 这样你就可以提交用户所做的textview的更改,检查rect是否仍然可以接受,如果不可以,则取消。

你将需要自己做这个。 基本上它会这样工作:

  1. 在你的UITextViewDelegatetextView:shouldChangeTextInRange:replacementText:方法中查找当前文本的大小(例如NSString sizeWithFont:constrainedToSize: textView:shouldChangeTextInRange:replacementText:
  2. 如果大小大于允许返回FALSE,则返回TRUE。
  3. 如果用户input的内容超出允许的范围,请向用户提供自己的反馈意见。

编辑:由于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]; 

我创build了一个testingVC。 每次在UITextView中到达新行时,它都会增加一个行计数器。 据我所知,你想限制你的文字input不超过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 

您可以检查边界矩形的大小,如果太大,请调用撤消pipe理器撤消最后一个操作。 可以是粘贴操作或input文本或换行符。

这是一个快速入侵,检查文本的高度是否太接近textView的高度。 还要检查textView矩形是否包含文本矩形。 您可能需要更多地摆弄这个以适应您的需求。

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

另一种select – 在这里我们检查大小,如果它太大,防止任何新的字符被input,除非它的删除。 不漂亮,因为这也防止如果超过高度在顶部填充线。

 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类

它通过Textviews文本容器属性与UITextview一起使用

它有这个属性称为大小…

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

@property(nonatomic)CGSize size Discussion该属性定义从lineFragmentRectForProposedRect返回的布局区域的最大尺寸:atIndex:writingDirection:remainingRect :. 0.0或更小的值意味着没有限制。

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

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

这里是link.Please请参阅此。 https://github.com/jayaprada-behera/CustomTextView