Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Add "...Read More" to the end of UILabel

Tags:

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!

like image 974
ytpm Avatar asked Aug 31 '15 10:08

ytpm


2 Answers

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

Readmore label output

NOTE: - Action is not included for Readmore

like image 61
ramchandra n Avatar answered Sep 17 '22 06:09

ramchandra n


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!

like image 38
ytpm Avatar answered Sep 21 '22 06:09

ytpm