I have a UITextView in my screen, the user should fill only two lines, and when the user in the second line the return key should turn into Done.
How can I limit the number of lines in UITextView? I searched a lot, and no results was useful!
I found this answer for Swift:
locationNoteTextView.textContainer.maximumNumberOfLines = 2
self.locationNoteTextView.textContainer.lineBreakMode = NSLineBreakMode.ByClipping
It didn't work, by this way the user can enter infinity character, but what viewed on the screen are just two lines!
So if you tried to print the text of textView you will find a disaster text.
For iOS 7+
textView.textContainer.maximumNumberOfLines = 10;
textView.textContainer.lineBreakMode = NSLineBreakByTruncatingTail;
OR
textView.textContainer.maximumNumberOfLines = 10;
[textView.layoutManager textContainerChangedGeometry:textView.textContainer];
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;
}
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)
I updated @Abhinav code to Swift 5, also added support to limit char count.
import UIKit
class ViewController: UIViewController {
@IBOutlet private var textView: UITextView!
private let maxNumberOfLines = 4
private let maxCharactersCount = 100
override func viewDidLoad() {
super.viewDidLoad()
textView.delegate = self
}
}
extension ViewController: UITextViewDelegate {
private func textView(_ textView: UITextView, newText: String, underLineLimit: Int) -> Bool {
guard let textViewFont = textView.font else {
return false
}
let rect = textView.frame.inset(by: textView.textContainerInset)
let textWidth = rect.width - 2.0 * textView.textContainer.lineFragmentPadding
let boundingRect = newText.size(constrainedToWidth: textWidth,
font: textViewFont)
let numberOfLines = boundingRect.height / textViewFont.lineHeight
let underLimit = Int(numberOfLines) <= underLineLimit
return underLimit
}
func textView(_ textView: UITextView,
shouldChangeTextIn range: NSRange,
replacementText text: String) -> Bool {
let newText = textView.text.replacingCharacters(in: range, with: text)
let underLineLimit = self.textView(textView, newText: newText, underLineLimit: maxNumberOfLines)
let underCharacterLimit = newText.count <= maxCharactersCount
return underLineLimit && underCharacterLimit
}
}
extension String {
func replacingCharacters(in range: NSRange, with string: String) -> String {
let currentNSString = self as NSString
return currentNSString.replacingCharacters(in: range, with: string)
}
func size(constrainedToWidth width: CGFloat, font: UIFont) -> CGSize {
let nsString = self as NSString
let boundingSize = CGSize(width: width, height: .greatestFiniteMagnitude)
return nsString
.boundingRect(with: boundingSize,
options: .usesLineFragmentOrigin,
attributes: [.font: font],
context: nil)
.size
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With