Reputation:
Using the following code I'm attempting to limit the height of my UITextView
by disallowing the user to enter characters once the UITextView
is beyond a certain content size. But the problem is that with the current code, the last character after the height limit is written anyway such that one character ends up on its own line alone and that line is beyond the height limit.
How I can fix my code so that the text doesn't go past my height limit?
func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {
var frame:CGRect = textView.frame;
frame.size.height = textView.contentSize.height;
if(frame.size.height <= 33.0){
return true
}
else {
return false
}
}
Upvotes: 1
Views: 1063
Reputation: 78
Thanks @lyndsey-scott, Below is the same code updated for latest sdk in xcode 9.1. Made minor edits (replaced max height as a variable)
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
// Combine the new text with the old
let combinedText = (textView.text as NSString).replacingCharacters(in: range, with: text)
// Create attributed version of the text
let attributedText = NSMutableAttributedString(string: combinedText)
let font = textView.font ?? UIFont.systemFont(ofSize: 12.0)
attributedText.addAttribute(NSAttributedStringKey.font, value: 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 = CGSize(width: textView.frame.size.width - padding * 2, height: CGFloat.greatestFiniteMagnitude)
// Get the bounding rect of the attributed text in the
// given frame
let boundingRect = attributedText.boundingRect(with: 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 height limit, append the text
if (boundingRect.size.height + padding * 2 <= MyViewController.maximumHeaderHeight){
return true
} else {
return false
}
}
Upvotes: 0
Reputation: 37300
The problem with your current code is that you're using textView.contentSize.height
for comparison when your text view has yet to contain the replacement text; so as your code stands, if the text view's current content size is <= 33, it will still allow you to enter characters (i.e. return true
) such that those returned characters can in fact breach the text view's height limit.
Update: I didn't really like my original answer because I thought boundingRectWithSize
would provide a cleaner solution. The problem was though, that it wasn't working for me and the text would go a little past the line limit...until now. The trick is to factor in the text container's padding.
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 height limit, append the text
if (boundingRect.size.height + padding * 2 <= 33.0){
return true
}
else {
return false
}
}
Original Solution:
To fix this with a solution as close to your current code as possible, you could duplicate your text view, append the new text to the old, and then return true only if the updated text view inclusive of the new text has a size less than the height limit, ex:
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 a duplicate of the text view with the same frame and font
let duplicateTextView = UITextView(frame: textView.frame)
duplicateTextView.font = textView.font
// Set the text view to contain the tentative new version of the text
duplicateTextView.text = combinedText
// Use sizeToFit in order to make the text view's height fit the text exactly
duplicateTextView.sizeToFit()
// Then use the duplicate text view's height for the comparison
if(duplicateTextView.frame.size.height <= 33.0){
return true
}
else {
return false
}
}
Upvotes: 1