Question

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

  1. maxLinesInTextView is an int variable represent the maximum number of lines you want.
  2. 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.
  3. 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:.
Was it helpful?

Solution 2

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.

OTHER TIPS

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

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
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top