I have a UILabel
and in some cases the text is longer then the UILabel
itself, so I see the text as "bla bla bla..."
I want to add a ...Read More
button text at the end of the UILabel
..
I've read some posts but they offer solutions that are not good to me, for example: to calculate how many characters will enter the UILabel
, but with the font i'm using each character has a different width.
How can I manage to do that?
Thanks in advance!
Swift4 (IOS 11.2)
Readmore at the end of the label without action
extension UILabel { func addTrailing(with trailingText: String, moreText: String, moreTextFont: UIFont, moreTextColor: UIColor) { let readMoreText: String = trailingText + moreText let lengthForVisibleString: Int = self.visibleTextLength let mutableString: String = self.text! let trimmedString: String? = (mutableString as NSString).replacingCharacters(in: NSRange(location: lengthForVisibleString, length: ((self.text?.count)! - lengthForVisibleString)), with: "") let readMoreLength: Int = (readMoreText.count) let trimmedForReadMore: String = (trimmedString! as NSString).replacingCharacters(in: NSRange(location: ((trimmedString?.count ?? 0) - readMoreLength), length: readMoreLength), with: "") + trailingText let answerAttributed = NSMutableAttributedString(string: trimmedForReadMore, attributes: [NSAttributedStringKey.font: self.font]) let readMoreAttributed = NSMutableAttributedString(string: moreText, attributes: [NSAttributedStringKey.font: moreTextFont, NSAttributedStringKey.foregroundColor: moreTextColor]) answerAttributed.append(readMoreAttributed) self.attributedText = answerAttributed } var visibleTextLength: Int { let font: UIFont = self.font let mode: NSLineBreakMode = self.lineBreakMode let labelWidth: CGFloat = self.frame.size.width let labelHeight: CGFloat = self.frame.size.height let sizeConstraint = CGSize(width: labelWidth, height: CGFloat.greatestFiniteMagnitude) let attributes: [AnyHashable: Any] = [NSAttributedStringKey.font: font] let attributedText = NSAttributedString(string: self.text!, attributes: attributes as? [NSAttributedStringKey : Any]) let boundingRect: CGRect = attributedText.boundingRect(with: sizeConstraint, options: .usesLineFragmentOrigin, context: nil) if boundingRect.size.height > labelHeight { var index: Int = 0 var prev: Int = 0 let characterSet = CharacterSet.whitespacesAndNewlines repeat { prev = index if mode == NSLineBreakMode.byCharWrapping { index += 1 } else { index = (self.text! as NSString).rangeOfCharacter(from: characterSet, options: [], range: NSRange(location: index + 1, length: self.text!.count - index - 1)).location } } while index != NSNotFound && index < self.text!.count && (self.text! as NSString).substring(to: index).boundingRect(with: sizeConstraint, options: .usesLineFragmentOrigin, attributes: attributes as? [NSAttributedStringKey : Any], context: nil).size.height <= labelHeight return prev } return self.text!.count } }
Swift 4.2
extension UILabel { func addTrailing(with trailingText: String, moreText: String, moreTextFont: UIFont, moreTextColor: UIColor) { let readMoreText: String = trailingText + moreText let lengthForVisibleString: Int = self.vissibleTextLength let mutableString: String = self.text! let trimmedString: String? = (mutableString as NSString).replacingCharacters(in: NSRange(location: lengthForVisibleString, length: ((self.text?.count)! - lengthForVisibleString)), with: "") let readMoreLength: Int = (readMoreText.count) let trimmedForReadMore: String = (trimmedString! as NSString).replacingCharacters(in: NSRange(location: ((trimmedString?.count ?? 0) - readMoreLength), length: readMoreLength), with: "") + trailingText let answerAttributed = NSMutableAttributedString(string: trimmedForReadMore, attributes: [NSAttributedString.Key.font: self.font]) let readMoreAttributed = NSMutableAttributedString(string: moreText, attributes: [NSAttributedString.Key.font: moreTextFont, NSAttributedString.Key.foregroundColor: moreTextColor]) answerAttributed.append(readMoreAttributed) self.attributedText = answerAttributed } var vissibleTextLength: Int { let font: UIFont = self.font let mode: NSLineBreakMode = self.lineBreakMode let labelWidth: CGFloat = self.frame.size.width let labelHeight: CGFloat = self.frame.size.height let sizeConstraint = CGSize(width: labelWidth, height: CGFloat.greatestFiniteMagnitude) let attributes: [AnyHashable: Any] = [NSAttributedString.Key.font: font] let attributedText = NSAttributedString(string: self.text!, attributes: attributes as? [NSAttributedString.Key : Any]) let boundingRect: CGRect = attributedText.boundingRect(with: sizeConstraint, options: .usesLineFragmentOrigin, context: nil) if boundingRect.size.height > labelHeight { var index: Int = 0 var prev: Int = 0 let characterSet = CharacterSet.whitespacesAndNewlines repeat { prev = index if mode == NSLineBreakMode.byCharWrapping { index += 1 } else { index = (self.text! as NSString).rangeOfCharacter(from: characterSet, options: [], range: NSRange(location: index + 1, length: self.text!.count - index - 1)).location } } while index != NSNotFound && index < self.text!.count && (self.text! as NSString).substring(to: index).boundingRect(with: sizeConstraint, options: .usesLineFragmentOrigin, attributes: attributes as? [NSAttributedString.Key : Any], context: nil).size.height <= labelHeight return prev } return self.text!.count } }
Usage
let readmoreFont = UIFont(name: "Helvetica-Oblique", size: 11.0) let readmoreFontColor = UIColor.blue DispatchQueue.main.async { self.yourLabel.addTrailing(with: "... ", moreText: "Readmore", moreTextFont: readmoreFont!, moreTextColor: readmoreFontColor) }
Result
NOTE: - Action is not included for Readmore
So this is what I did to add the Read More... button to the UITextView
, UITextField
or UILabel
:
- (void)addReadMoreStringToUILabel:(UILabel*)label { NSString *readMoreText = @" ...Read More"; NSInteger lengthForString = label.text.length; if (lengthForString >= 30) { NSInteger lengthForVisibleString = [self fitString:label.text intoLabel:label]; NSMutableString *mutableString = [[NSMutableString alloc] initWithString:label.text]; NSString *trimmedString = [mutableString stringByReplacingCharactersInRange:NSMakeRange(lengthForVisibleString, (label.text.length - lengthForVisibleString)) withString:@""]; NSInteger readMoreLength = readMoreText.length; NSString *trimmedForReadMore = [trimmedString stringByReplacingCharactersInRange:NSMakeRange((trimmedString.length - readMoreLength), readMoreLength) withString:@""]; NSMutableAttributedString *answerAttributed = [[NSMutableAttributedString alloc] initWithString:trimmedForReadMore attributes:@{ NSFontAttributeName : label.font }]; NSMutableAttributedString *readMoreAttributed = [[NSMutableAttributedString alloc] initWithString:readMoreText attributes:@{ NSFontAttributeName : Font(TWRegular, 12.), NSForegroundColorAttributeName : White }]; [answerAttributed appendAttributedString:readMoreAttributed]; label.attributedText = answerAttributed; UITagTapGestureRecognizer *readMoreGesture = [[UITagTapGestureRecognizer alloc] initWithTarget:self action:@selector(readMoreDidClickedGesture:)]; readMoreGesture.tag = 1; readMoreGesture.numberOfTapsRequired = 1; [label addGestureRecognizer:readMoreGesture]; label.userInteractionEnabled = YES; } else { NSLog(@"No need for 'Read More'..."); } }
There is a use of fitString:intoLabel
method which can be found here.
As for the UITagTapGestureRecognizer
is just a normal UITapGestureRecognizer
subclass with a NSInteger
property called tag. I did that because I want to identify which Read More...
were clicked in I case I have more than one in the same UIViewController
. You can use a normal UITapGestureRecognizer
.
Enjoy!
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