iOS: How to limit number of lines of UITextView using Swift? - ios

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?

Related

UITextView change selectRange always crash

When user click the passage in textView, I want to change the cursor to the linebreak, but when I change selectionRange is always failed.
I know the reason, but I must change the selectedRange in func: textViewDidChangeSelection:(UITextView*)textView
How can I change the selectedRange?
here is the code
-(void)textViewDidChangeSelection:(UITextView *)textView
// paragLocations: it contain all "\n" locations
NSArray* paragLocations = [self ParagraphLocationsWithText:textView Pattern:translatePragraphLinebreak];
// location :According to user selection,The nearest "\n" location
NSUInteger nearestLocation = [self ClickParagraphEndBreakLoctionWithSelectLocation:textView.selectedRange.location withParagLocations:paragLocations];
//Then
//1. here I change the textView.selectedRange
textView.selectedRange = NSMakeRange(nearestLocation, 0);
//2. here I change the cursorPosition
CGFloat cursorPosition = [self caretRectForPosition:textView.selectedTextRange.start].origin.y;
[textView setContentOffset:CGPointMake(0, cursorPosition) animated:YES];
// but In step 1 ,It changed textView.selectedRange,So this func will do it again,and then again,until the nearestLocation became the paragLocations.lastObject.
/*
so the question is how to break this Infinite loop ?
should I change selectedRange In this func?
I want to changed the selectedRange base on User select In textview
*/
sorry about my poor english.
You can try the following code, it works for me fine.
- (void)textViewDidChangeSelection:(UITextView *)textView {
UITextRange *selectRange = [textView selectedTextRange];
NSString *selectedText = [textView textInRange:selectRange];
}

How to get n lines of text from UITextView in iOS

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.

Limit the number of line in UITextView

Question
Is there any way to "accurately" limit the number of line in UITextView for target iOS 5.0?
What I had tried
As I had search in stack overflow. I had found these question been ask before in links below.
In UITextView, how to get the point that next input will begin another line
Limit the number of lines for UITextview
Limit number of lines in UITextView
But I still can't get the accurate number of line in UITextView when I tried to decide whether to return YES or NO in textView:shouldChangeTextInRange:replacementText:.
I had tried used the code which is the answer of Limiting text in a UITextView and the code after modified (remove -15 in the answer) is showing below.
- (BOOL)textView:(UITextView *)aTextView shouldChangeTextInRange:(NSRange)aRange replacementText:(NSString*)aText
{
NSString* newText = [aTextView.text stringByReplacingCharactersInRange:aRange withString:aText];
// TODO - find out why the size of the string is smaller than the actual width, so that you get extra, wrapped characters unless you take something off
CGSize tallerSize = CGSizeMake(aTextView.frame.size.width,aTextView.frame.size.height*2); // pretend there's more vertical space to get that extra line to check on
CGSize newSize = [newText sizeWithFont:aTextView.font constrainedToSize:tallerSize lineBreakMode:UILineBreakModeWordWrap];
if (newSize.height > aTextView.frame.size.height)
{
[myAppDelegate beep];
return NO;
}
else
return YES;
}
I also figure out a way to get the number of line in UITextView. The way is to calculate by contentSize property like textView.contenSize.height/font.lineHeight. This method can get the accurate number of lines in UITextView. But the problem is that contentSize get in textView:shouldChangeTextInRange:replacementText: and textViewDidChange: is the old contentSize. So I still can't limit the number of lines in UITextView.
Solution I used
This is kind of workaround but at least it work.
Step 1
At first you need to create a temporary new UITextView with all the same as the original UITextView but setting the temporary UITextView hidden in .xib file. In this sample code I name the temporary UITextView as tempTextInputView
Step 2
Add new referencing outlet to .h file like
#property (retain, nonatomic) IBOutlet UITextView *tempTextInputView;// Use to calculate the number of lines in UITextView with new text
Step 3
Add code below.
- (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;
}
}
Discussion
maxLinesInTextView is an int variable represent the maximum number of lines you want.
I use a temporary UITextView to setting new text is because when I setting the new text simply in the original UITextView, I got some problem when I typing in ChuYin(注音) keyboard which is a Traditional Chinese input method.
I still using textView:shouldChangeTextInRange:replacementText: but not textViewDidChange: is because I got some problem when cache the text before modify with a global NSString and replace the UITextView.text with that global NSString in textViewDidChange:.
Here's how you can use the UITextViewDelegate shouldChangeTextInRange: method to limit the text entry to the height of the text view:
func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {
// Combine the new text with the old
let combinedText = (textView.text as NSString).stringByReplacingCharactersInRange(range, withString: text)
// Create attributed version of the text
let attributedText = NSMutableAttributedString(string: combinedText)
attributedText.addAttribute(NSFontAttributeName, value: textView.font, range: NSMakeRange(0, attributedText.length))
// Get the padding of the text container
let padding = textView.textContainer.lineFragmentPadding
// Create a bounding rect size by subtracting the padding
// from both sides and allowing for unlimited length
let boundingSize = CGSizeMake(textView.frame.size.width - padding * 2, CGFloat.max)
// Get the bounding rect of the attributed text in the
// given frame
let boundingRect = attributedText.boundingRectWithSize(boundingSize, options: NSStringDrawingOptions.UsesLineFragmentOrigin, context: nil)
// Compare the boundingRect plus the top and bottom padding
// to the text view height; if the new bounding height would be
// less than or equal to the text view height, append the text
if (boundingRect.size.height + padding * 2 <= textView.frame.size.height){
return true
}
else {
return false
}
}
As I have mentioned in my answer here, I advise against using shouldChangeCharactersInRange: since it is invoked before the text is actually changed.
Using the textViewDidChangeMethod: makes more sense, since it is invoked after the text actually changes. From there you can easily decide what to do next.
One options is to modify the textView yourself in the shouldChangeTextInRange delegate method and always return NO (because you already did the work for it).
- (BOOL)textView:(UITextView *)aTextView shouldChangeTextInRange:(NSRange)aRange replacementText:(NSString*)aText
{
NSString* oldText = aTextView.text;
NSString* newText = [aTextView.text stringByReplacingCharactersInRange:aRange withString:aText];
aTextView.text = newText;
if(/*aTextView contentSize check herer for number of lines*/)
{
//If it's now too big
aTextView.text = oldText;
}
return NO
}

Restrict the user to enter max 50 words in UITextView iOS

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.

Getting the Y coordinate for every occurrence of a particular string in a UITextView for line numbering

I'm trying to get the y positions of every \n character in a UITextView so I can align line numberings alongside the UITextView for accurate line numbers. What I'd like to do is get an array of the Y coordinates of every occurrence of \n.
At the moment, I can get the CGSize for the current word, but I'm not quite sure how I can adjust this in order to get the Y coordinate of every occurence of a particular word.
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
NSRange aRange = self.editorText.selectedRange;
if(range.location<self.editorText.text.length)
{
NSString * firstHalfString = [self.editorText.text substringToIndex:range.location];
NSString *temp = #"s";
CGSize s1 = [temp sizeWithFont:[UIFont systemFontOfSize:15]
constrainedToSize:CGSizeMake(self.view.bounds.size.width - 40, MAXFLOAT) // - 40 For cell padding
lineBreakMode:UILineBreakModeWordWrap]; // enter you textview font size
CGSize s = [firstHalfString sizeWithFont:[UIFont systemFontOfSize:15]
constrainedToSize:CGSizeMake(self.view.bounds.size.width - 40, MAXFLOAT) // - 40 For cell padding
lineBreakMode:UILineBreakModeWordWrap]; // enter you textview font size
//Here is the frame of your word in text view.
NSLog(#"xcoordinate=%f, ycoordinate =%f, width=%f,height=%f",s.width,s.height,s1.width,s1.height);
CGRect rectforword = CGRectMake(s.width, s.height, s1.width, s1.height);
// rectforword is rect of yourword
}
else
{
// Do what ever you want to do
}
return YES;
}
The main part I'm not sure about is getting each occurrence of the character in such a way that I can retrieve its coordinates.
Is there a way to do this? Or is there an easier way for me to implement line numbering?
Is custom parsing your NSString and appending #) to the start of each line an option?
It's a non-trivial bit of string parsing and manipulating to accomplish, but it has the added benefit of not having to care at all about yCoordinates and other messy layout based parameters.

Resources