For an iOS application I am writing I am using an UITextView, where the user can insert a limited text.
To the textview there are 2 restrictions:
- Lines can be no longer than 30 characters
- There can be only 20 lines of text in the UITextView.
So in short, the maximum is 20 lines of 30 characters.
When a user is typing some text in the UITextView and the current sentence is 30 characters, I want it to automatically insert a new line \n (before the last word on that line) and force the last word and cursor to the line below.
When a user has 20 lines with 30 characters (or even simpler said: 20 lines, with 30 characters on the last line) I want the input to be blocked.
Now, most of this is fairly ‘simple’ but the code I have does not account for border cases, like inserting text in earlier lines.
I looked around the documentation from Apple, but I can not find a way to actually force this kind of Word-wrapping on a UITextView.
My try is to handle all this in the shouldChangeTextInRange delegate method (made the code a little more verbose, so it’s a bit easier to read).
#define MAX_LENGTH_LINE 30
#define MAX_LENGTH_ROWS 20
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
// Check for backspaces, they should always be allowed?
if ([text length] == 0 && ![text isEqualToString:@"\n"])
return YES;
NSArray* lines = [textView.text componentsSeparatedByString:@"\n"];
// Check if there are a maximum of lines and the last line is already maxed out
NSString* lastLine = [lines objectAtIndex:[lines count] - 1];
if (([lines count] == MAX_LENGTH_ROWS) &&
(lastLine != nil) &&
([lastLine length] > MAX_LENGTH_LINE) &&
([text length] > 0))
return NO;
if ((lastLine != nil) &&
([lastLine length] > MAX_LENGTH_LINE))
{
NSRange range = [textView.text rangeOfString:@" " options:NSBackwardsSearch];
NSRange breakRange = [textView.text rangeOfString:@"\n" options:NSBackwardsSearch];
if (breakRange.location == NSNotFound)
breakRange = NSMakeRange(0, 1);
if (range.location == NSNotFound) {
range = NSMakeRange(0, 1);
}
if (range.location > breakRange.location)
{
textView.text = [textView.text stringByReplacingCharactersInRange:NSMakeRange(range.location, 1) withString:@"\n"];
}
else
{
textView.text = [textView.text stringByAppendingString:@"\n"];
}
}
if ([text isEqualToString:@"\n"])
{
if ([lines count] == MAX_LENGTH_ROWS)
return NO;
else {
return YES;
}
NSRange range = NSMakeRange(textView.text.length - 1, 1);
[textView scrollRangeToVisible:range];
}
return YES;
}
In the meanwhile I have been at this for a while and I lost it at the moment. Anyone who can give some pointers to just limit the UITextView to the 20 lines / 30 characters limitation I want?
After a bit of fiddling around, created a control which contains a UITextView as a subview.
I let this control handle the text wrapping and forward the delegate methods to the view registered as a delegate.
It might help others, so I am posting a link to BitBucket here.
PS. It’s still very verbose, but that is to show how I solved this for my case.
https://bitbucket.org/depl0y/sbframework/src/master/SBFramework/Views/SBTextView?at=master