I have a non-scrollable UITextView with it's layoutManager maximumNumberOfLines set to 9, which works fine, but, I cannot seem to find a method in NSLayoutManager that restricts the text to not go beyond the frame of the UITextView.
Take for example in this screenshot, the cursor is on the 9th line (the 1st line is clipped at top of screenshot, so disregard that). If the user continues to type new characters, spaces, or hit the return key, the cursor continues off screen and the UITextView's string continues to get longer.
I don't want to limit the amount of characters of the UITextView, due to foreign characters being different sizes.
I've been trying to fix this for several weeks; I'd greatly appreciate any help.
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
Update, still not resolved
You can check the size of the bounding rectangle and if it is too big call the undo manager to undo the last action. Could be a paste operation or enter in text or new line character.
Here is a quick hack that checks if the height of the text is too close to the height of the textView. Also checks that the textView rect contains the text rect. You might need to fiddle with this some more to suit your needs.
-(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;
}
ANOTHER OPTION - here we check the size and if its too big prevent any new characters being typed in except if its a deletion. Not pretty as this also prevents filling a line at the top if the height is exceeded.
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;
}
boundingRectWithSize:options:attributes:context:
is not recommended for textviews, because it does not take various attributes of the textview (such as padding), and thus return an incorrect or imprecise value.
To determine the textview's text size, use the layout manager's usedRectForTextContainer:
with the textview's text container to get a precise rectangle required for the text, taking into account all required layout constraints and textview quirks.
CGRect rect = [self.textView.layoutManager usedRectForTextContainer:self.textView.textContainer];
I would recommend doing this in processEditingForTextStorage:edited:range:changeInLength:invalidatedRange:
, after calling the super
implementation. This would mean replacing the textview's layout manager by providing your own text container and setting its layout manager to your subclass' instance. This way you can commit the changes from the textview made by the user, check if the rect is still acceptable and undo if not.
You will need to do this yourself. Basically it would work like this:
UITextViewDelegate
's textView:shouldChangeTextInRange:replacementText:
method find the size of your current text (NSString sizeWithFont:constrainedToSize:
for example).EDIT: Since sizeWithFont:
is deprecated use boundingRectWithSize:options:attributes:context:
Example:
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];
I created a test VC. It increases a line counter every time a new line is reached in the UITextView. As I understand you want to limit your text input to no more than 9 lines. I hope this answers your question.
#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
There is a new Class in IOS 7 that works hand in hand with UITextviews which is the NSTextContainer Class
It works with UITextview through the Textviews text container property
it has this property called size ...
size Controls the size of the receiver’s bounding rectangle. Default value: CGSizeZero.
@property(nonatomic) CGSize size Discussion This property defines the maximum size for the layout area returned from lineFragmentRectForProposedRect:atIndex:writingDirection:remainingRect:. A value of 0.0 or less means no limitation.
I am still in the process of understanding it and trying it out but I believe it should resolve your issue.
No need to find number of lines. We can get all these things by calculating the cursor position from the textview and according to that we can minimize the UIFont of UITextView according to the height of UITextView.
Here is below link.Please refer this. https://github.com/jayaprada-behera/CustomTextView
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With