I am trying to restrict the user to enter max 50 words in UITextView. I tried solution by PengOne from this question1 . This works for me except user can enter unlimited chars in the last word.
So I thought of using regular expression. I am using the regular expression given by
VonC in this question2. But this does not allow me enter special symbols like , " # in the text view.
In my app , user can enter anything in the UITextView or just copy-paste from web page , notes , email etc.
Can anybody know any alternate solution for this ?
Thanks in advance.
This code should work for you.
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
return textView.text.length + (text.length - range.length) <= 50;
}
Do as suggested in "question2", but add # within the brackets so that you can enter that as well. May need to escape it if anything treats it as a special character.
You use [NSCharacterSet whitespaceCharacterSet] to calculate word.
- (BOOL) textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
static const NSUInteger MAX_NUMBER_OF_LINES_ALLOWED = 3;
NSMutableString *t = [NSMutableString stringWithString: self.textView.text];
[t replaceCharactersInRange: range withString: text];
NSUInteger numberOfLines = 0;
for (NSUInteger i = 0; i < t.length; i++) {
if ([[NSCharacterSet whitespaceCharacterSet] characterIsMember: [t characterAtIndex: i]]) {
numberOfWord++;
}
}
return (numberOfWord < 50);
}
The method textViewDidChangeSelection: is called when a section of text is selected or the selection is changed, such as when copying or pasting a section of text.
Related
I'm trying to make a fb messenger style new message formation uitextview.. as shown in the image..
Specifically, how to make the uitextview which is at the top of the tableview (showing list of selected friends). I want to make a uitextview with the following properties..
1) It expands/contracts as more names are added/removed to/from it.
2) The textview is editable - but not partially editable, i.e., a name is either wiped out by backspace or not (like how it happens in fb)
3) Possibly this editing happens in nice aesthetics (similar to fb, make the color of the entire text blue colored before backspacing it out)
It has been a while since I have used objective-c, but I believe this is correct. It may not be though. And yes I would eventually switch to swift.
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
//you should have an array of names as a property, not here.
NSMutableArray* nameArray = [#"sam"];
if ([text isEqual: #""]) {// this is delete
for (NSString *name in nameArray) {
NSRange nameRange = [textView.text rangeOfString:name];
if (nameRange.location == range.location) {
textView.text = [textView.text stringByReplacingCharactersInRange:nameRange withString:#""];
[nameArray removeObject:name]
return false;
}
}
}
return true;
}
Using the shouldChangeTextInRange delegate to detect certain phrases when typed in a UITextViewgives different range and text values when alternating between the stock keyboard and custom keyboards like SwiftKey (in identical scenarios).
-(BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {...}
For example:
The moment after I type: "ABCDEFG HIJ " without quotes and including the ending space using the stock keyboard, I find the range is equal to (11, 0). However, with SwiftKey, I'm getting a range of (10, 1). Additionally, the text will be equal to just the ending space using the stock keyboard, but will be equal to 'J' plus the space using SwiftKey. Any ideas why this is happening and how I can standardize my function's behavior given these differences if I want to properly detect certain phrases? (my goal is detecting the two words immediately before every space press)
EDIT: this is how I detect the two words in front of a space. My issue is that range is giving me different values when I use a non-stock keyboard
NSUInteger charsBackUntilSecondSpace = 0;
NSUInteger numSpacesFound = 0;
for (int i = (int) range.location - 1; i>= 0; i--) {
if([textView.text characterAtIndex:i] == ' ') {
numSpacesFound++;
}
if (numSpacesFound == 2) {
break;
}
else {
charsBackUntilSecondSpace++;
}
}
UITextRange *selectedTextRange = textView.selectedTextRange;
NSUInteger location = [textView offsetFromPosition:textView.beginningOfDocument
toPosition:selectedTextRange.start];
NSRange inFrontOfCursorRange = NSMakeRange (location - charsBackUntilSecondSpace, charsBackUntilSecondSpace);
my goal is detecting the two words immediately before every space
press
You can achieve it like that below:-
NSInteger lenPos=(range.length > 2) ? 2 : range.length;
NSString *txt=[textView.text substringWithRange:NSMakeRange(range.length-lenPos-1, lenPos)] ;
I am making an application in which there is an UITextView that only allows users to type in 2 lines of text. So the text will not reach the third line as the UITextView would ask it to stop. Does anyone know how to approach this in Swift programming? I have found there is a way to achieve this but written in Objective-C code
- (BOOL) textView:(UITextView *)textView
shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
NSString *newText = [textView.text stringByReplacingCharactersInRange:range withString:text];
_tempTextInputView.text = newText;
// Calcualte the number of lines with new text in temporary UITextView
CGRect endRectWithNewText = [_tempTextInputView caretRectForPosition:_tempTextInputView.endOfDocument];
CGRect beginRectWithNewText = [_tempTextInputView caretRectForPosition:_tempTextInputView.beginningOfDocument];
float beginOriginY = beginRectWithNewText.origin.y;
float endOriginY = endRectWithNewText.origin.y;
int numberOfLines = (endOriginY - beginOriginY)/textView.font.lineHeight + 1;
if (numberOfLines > maxLinesInTextView) {// Too many lines
return NO;
}else{// Number of lines will not over the limit
return YES;
}
}
I have been struggling with the equivalent code in Swift especially this line of code:
NSString *newText = [textView.text stringByReplacingCharactersInRange:range withString:text];
I just couldn't figure out how to deal with NSRange in Swift. Could someone please help me ?
Can you just set the frame size for two lines and limit the total number of characters that can be entered?
In my application i need to restrict to user enter 7 lines only in UITextView. When he tries enter text in 8 line we need to stop allowing editing and show an alert message. Its working fine by using CGSize of getting UITextView text.
But when user enters text in UITextView as Paste its not allowing if text is more than 7 lines. As per requirement i need to get the 7 lines of Text from entred (Copied & Pasted ) in to UITextView.
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)string
{
NSString *temp = [textView.text stringByReplacingCharactersInRange:range withString:string]
CGSize size = [temp sizeWithFont:textView.font constrainedToSize:CGSizeMake(textView.frame.size.width,999) lineBreakMode:UILineBreakModeWordWrap];
int numLines = size.height / textView.font.lineHeight;
if (numLines <= 8)
{
return true;
}
//Alert
return false;
}
Please help me in this issue.
Thanks in Advance.
You could use the UITextViewTextDidChangeNotification notification to alert you on other changes like text being pasted in. (referenced at the bottom of this page)
Or, there is also this handy category on UITextView that allows it to work with the UITextField UIControl events that can be found here.
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?
This may be counter to your overall goals, but the simplest answer in my head is to reparse the string every time the user adds a character. Then it wouldn't matter where the character was added. Instead of doing all this in shouldChangeTextInRange: do it in textViewDidChange:. You will need to be a UITextViewDelegate and you will need a single class NSString to hold the last successful user text update in case your user is trying to add a character beyond the allowed limit.
Something like this:
-(void)textViewDidChange:(UITextView *)textView
{
//Get the textview without any new line characters
NSMutableString *temporaryString = [NSMutableString stringWithString:[textView.text stringByReplacingOccurrencesOfString:#"\n" withString:#" "]];
bool updatePossible = true;
int i = 0, numberOfLinesSoFar = 0;
while(i + 30 < [temporaryString length])
{
//Go 30 characters in and start the reverse search for a word separation
i += 30;
int j = i;
//Get the location of the final word separation in the current line
while(j >= i && [temporaryString characterAtIndex:j] != ' ')
{
j--;
}
//This means we found a word separation
if(j > i)
{
i = j;
[temporaryString replaceCharactersInRange:NSMakeRange(i,1) withString:#"\n"];
}
//We didn't find a word separation
else
{
//Here we will just have to break the line at 30.
[temporaryString insertString:#"\n" atIndex:i];
}
numberOfLinesSoFar++;
//Check if we just wrote line 20 and still have characters to go.
if(numberOfLinesSoFar > 19 && i < [temporaryString length])
{
//Revert user change to the last successful character addition
textView.text = lastSuccessfulViewString;
updatePossible = false;
break;
}
}
if(updatePossible)
{
//If we are within the limits then update the global string (for undoing character additions) and the textview
textView.text = temporaryString;
lastSuccessfulViewString = temporaryString;
}
}
Now this won't allow the user to put in their own new line characters, but that could be handled with a couple if then statements.
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