How to limit the number of lines in UITextView?

You need to implement textView:shouldChangeTextInRange:replacementText:. This method is called whenever the text is going to change. You can access the current content of the text view using its text property.

Construct the new content from the passed range and replacement text with [textView.text stringByReplacingCharactersInRange:range withString:replacementText].

You can then count the number of lines and return YES to allow the change or NO to reject it.

EDIT: On OP request:

Swift:

func sizeOfString (string: String, constrainedToWidth width: Double, font: UIFont) -> CGSize {
    return (string as NSString).boundingRectWithSize(CGSize(width: width, height: DBL_MAX),
        options: NSStringDrawingOptions.UsesLineFragmentOrigin,
        attributes: [NSFontAttributeName: font],
        context: nil).size
}


func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {
    let newText = (textView.text as NSString).stringByReplacingCharactersInRange(range, withString: text)
    var textWidth = CGRectGetWidth(UIEdgeInsetsInsetRect(textView.frame, textView.textContainerInset))
    textWidth -= 2.0 * textView.textContainer.lineFragmentPadding;

    let boundingRect = sizeOfString(newText, constrainedToWidth: Double(textWidth), font: textView.font!)
    let numberOfLines = boundingRect.height / textView.font!.lineHeight;

    return numberOfLines <= 2;
}

Obj-C

- (CGSize) sizeOfString:(NSString*)str constrainedToWidth:(CGFloat)width andFont:(UIFont*)font
{
    return [str boundingRectWithSize:CGSizeMake(width, DBL_MAX)
                              options:NSStringDrawingUsesLineFragmentOrigin
                           attributes:@{NSFontAttributeName: font}
                              context:nil].size;
}

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
    NSString *newText = [textView.text stringByReplacingCharactersInRange:range withString:text];
    CGFloat textWidth = CGRectGetWidth(UIEdgeInsetsInsetRect(textView.frame, textView.textContainerInset));
    textWidth -= 2.0 * textView.textContainer.lineFragmentPadding;
    CGSize boundingRect = [self sizeOfString:newText constrainedToWidth:textWidth andFont:textView.font];
    int numberOfLines = boundingRect.height / textView.font.lineHeight;
    return numberOfLines <= 2;
}

For iOS 7+

textView.textContainer.maximumNumberOfLines = 10;
textView.textContainer.lineBreakMode = NSLineBreakByTruncatingTail;

OR

textView.textContainer.maximumNumberOfLines = 10;
[textView.layoutManager textContainerChangedGeometry:textView.textContainer];

In case anyone wants realtime line number count, this is how you do it with RxSwift & RxCocoa

    let disposeBag = DisposeBag()

    textView.rx_text.asDriver().driveNext { text in

        let text = text as NSString

        var textWidth: CGFloat = CGRectGetWidth(UIEdgeInsetsInsetRect(self.textView.frame, self.textView.textContainerInset))

        textWidth -= 2.0 * self.textView.textContainer.lineFragmentPadding

        let textAttributes = [NSFontAttributeName: UIFont(name: “Set your font here”, size: 26.0)!]

        let boundingRect: CGRect = text.boundingRectWithSize(CGSizeMake(textWidth, 0), options: [NSStringDrawingOptions.UsesLineFragmentOrigin, NSStringDrawingOptions.UsesFontLeading], attributes: textAttributes, context: nil)

        let font = UIFont(name: “Set your font here”, size: 26.0)

        guard let lineHeight = font?.lineHeight else {return}

        let numberOfLines = CGRectGetHeight(boundingRect) / lineHeight

        if numberOfLines <= 2 {


            self.textView.typingAttributes = [NSFontAttributeName: UIFont(name: “Set your font here”, size: 26.0)!, NSForegroundColorAttributeName: “Set your color here”]

        } else {

            self.textView.typingAttributes = [NSFontAttributeName: UIFont(name: “Set your font here”, size: 18.0)!, NSForegroundColorAttributeName: “Set your color here”]

        }

    }.addDisposableTo(disposeBag)