ios objective-c uilabel nsattributedstring

ios - Agregue "... Leer más" al final de UILabel



objective-c nsattributedstring (14)

Tengo un UILabel y, en algunos casos, el texto es más largo que el UILabel sí mismo, así que veo el texto como "bla bla bla..." Quiero agregar un texto de botón ...Read More al final del UILabel . .

He leído algunas publicaciones, pero ofrecen soluciones que no son buenas para mí, por ejemplo: para calcular cuántos caracteres ingresarán al UILabel , pero con la fuente que estoy usando, cada carácter tiene un ancho diferente.

¿Cómo puedo hacer eso?

¡Gracias por adelantado!


¿Sabes que no hay acción táctil de UILabel? así que no puede tocar ''... Leer más'' si el texto completo en un UILabel.

Nota: mi solución es agregar un botón de fondo claro al final de UILabel.


Para Acción en la etiqueta, si usa un CollectionView o TableView, podría usar un método delegado para realizar la acción.

func showMore(cell: CustomCell) { guard let indexPath = self.tableView.indexPath(for: cell) else { return } let cell = tableView.cellForRow(at: indexPath) as! CustomCell tableView.beginUpdates() cell.label.text = "your complete text" tableView.endUpdates() }

Esto actualiza la etiqueta y muestra el texto completo según sea necesario. Usando la respuesta de Lance Samaria y agregando la acción para la celda.


Entonces, esto es lo que hice para agregar el botón Leer más ... al UITextView , UITextField o 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''..."); } }

Hay un uso del método fitString:intoLabel que se puede encontrar here .

En cuanto a UITagTapGestureRecognizer es solo una subclase normal de UITapGestureRecognizer con una propiedad NSInteger llamada etiqueta. Lo hice porque quiero identificar en qué Read More... se hizo clic en caso de que tenga más de uno en el mismo UIViewController . Puede usar un UITapGestureRecognizer normal.

¡Disfrutar!


Esto funciona para Swift 4.2

Aquí hay una versión más segura de la respuesta de @ ramchandran porque no sabe cuántos caracteres ingresará el usuario.

En su respuesta, si la longitud de la cadena que ingresó el usuario es menor que la longitud del texto que decida usar para ... Readmore entonces se bloqueará. Por ejemplo, así es como lo usas

if yourLabel.text!.count > 1 { 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) } }

En el ejemplo anterior, la salida de ... Readmore es de 12 caracteres en total. Si la cadena que ingresó el usuario era yourLabel.text = "12345678" , el texto de la cadena solo tendría 8 caracteres. Se bloqueará porque el rango que usa ((trimmedString?.count ?? 0) - readMoreLength) en la línea a continuación produciría un resultado negativo:

// “12345678” minus “... Readmore” = negative four (8 - 12 = -4) let trimmedForReadMore: String = (trimmedString! as NSString).replacingCharacters(in: NSRange(location: ((trimmedString?.count ?? 0) - readMoreLength), length: readMoreLength), with: "") + trailingText

Agregué un control de seguridad para asegurarme de que si la cadena ingresada es menor o igual que el número de caracteres para lo que decida usar como ... Readmore más, volverá y la línea que causará el bloqueo nunca se alcanzará:

// trimmedString is the string the user entered guard let safeTrimmedString = trimmedString else { return } if safeTrimmedString.count <= readMoreLength { return }

Está ubicado en el centro de la función addTrailing

extension UILabel{ func addTrailing(with trailingText: String, moreText: String, moreTextFont: UIFont, moreTextColor: UIColor) { let readMoreText: String = trailingText + moreText if self.visibleTextLength == 0 { return } let lengthForVisibleString: Int = self.visibleTextLength if let myText = self.text { let mutableString: String = myText let trimmedString: String? = (mutableString as NSString).replacingCharacters(in: NSRange(location: lengthForVisibleString, length: myText.count - lengthForVisibleString), with: "") let readMoreLength: Int = (readMoreText.count) guard let safeTrimmedString = trimmedString else { return } if safeTrimmedString.count <= readMoreLength { return } print("this number /(safeTrimmedString.count) should never be less/n") print("then this number /(readMoreLength)") // "safeTrimmedString.count - readMoreLength" should never be less then the readMoreLength because it''ll be a negative value and will crash let trimmedForReadMore: String = (safeTrimmedString as NSString).replacingCharacters(in: NSRange(location: safeTrimmedString.count - 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 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) if let myText = self.text { let attributes: [AnyHashable: Any] = [NSAttributedString.Key.font: font] let attributedText = NSAttributedString(string: myText, 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 = (myText as NSString).rangeOfCharacter(from: characterSet, options: [], range: NSRange(location: index + 1, length: myText.count - index - 1)).location } } while index != NSNotFound && index < myText.count && (myText as NSString).substring(to: index).boundingRect(with: sizeConstraint, options: .usesLineFragmentOrigin, attributes: attributes as? [NSAttributedString.Key : Any], context: nil).size.height <= labelHeight return prev } } if self.text == nil { return 0 } else { return self.text!.count } } }


La etiqueta Tttattributed tiene esta característica

https://github.com/TTTAttributedLabel/TTTAttributedLabel

Debe establecer el token de "truncamiento" como "leer más ..."

Ver

atribuidoTruncaciónToken

var subTitleLabel = TTTAttributedLabel(frame : frame) self.addSubview(subTitleLabel) var trunc = NSMutableAttributedString(string: "...more") trunc.addAttribute(NSFontAttributeName, value: UIFont.systemFontOfSize(12), range: NSMakeRange(0, 7)) trunc.addAttribute(NSForegroundColorAttributeName, value: UIColor.blueColor(), range: NSMakeRange(0, 7)) subTitleLabel.attributedTruncationToken = trunc subTitleLabel.numberOfLines = 1 subTitleLabel.autoresizingMask = UIViewAutoresizing.FlexibleHeight | UIViewAutoresizing.FlexibleWidth


Mi solución es crear un UIButton (nombre Leer más) en la parte inferior derecha y debajo de UILabel . Después de eso, compruebo que el UILabel está truncado o no para mostrar u ocultar el UIButton

CGSize sizeOfText = [self.label.text boundingRectWithSize: CGSizeMake(self.label.intrinsicContentSize.width, CGFLOAT_MAX) options: (NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading) attributes: [NSDictionary dictionaryWithObject:self.label.font forKey:NSFontAttributeName] context: nil].size; if (self.label.intrinsicContentSize.height < ceilf(sizeOfText.height)) { // label is truncated self.readmoreButton.hidden = NO; // show Read more button }else{ self.readmoreButton.hidden = YES; }

=== Versión Swift 3

let textheight = self.label.text?.height(withConstrainedWidth: self.label.frame.width, font: self.label.font) if self.label.intrinsicContentSize.height < textheight! { self.readmoreButton.isHidden = false }else{ self.readmoreButton.isHidden = true }

agregue esta extensión:

extension String { func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat { let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude) let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil) return boundingBox.height }

}

Espero que esto ayude


Puedes probar la tercera biblioteca ExpandableLable

Establezca la clase personalizada de su UILabel en ExpandableLabel y establezca el número deseado de líneas y texto contraído:

expandableLabel.numberOfLines = 5 expandableLabel.collapsedAttributedLink = NSAttributedString(string: "more") expandableLabel.ellipsis = NSAttributedString(string: "...") // update label expand or collapse state expandableLabel.collapsed = true

Es posible que necesite configurar un delegate para recibir notificaciones en caso de que se haya tocado el enlace.


Usando el método - boundingRectWithSize: opciones: atributos: contexto: y pasando su fuente como clave NSAttributedString para NSAttributedString le dará el rect correcto necesario.

A partir de eso, debe verificar si es más grande que los límites de su etiqueta menos el desplazamiento. Solo si es así, debe recortar el texto y mostrar Read More al final.


este método es útil para showless y showAll con imagen de flecha hacia arriba: agregue tapgesture en la etiqueta

let tap = UITapGestureRecognizer(target: self, action: #selector(self.tapFunction)) Urlabel.isUserInteractionEnabled = true Urlabel.addGestureRecognizer(tap) @objc func tapFunction(sender:UITapGestureRecognizer) { }


Para acción

extension ViewController: TTTAttributedLabelDelegate { func attributedLabel(_ label: TTTAttributedLabel!, didSelectLinkWithTransitInformation components: [AnyHashable : Any]!) { if let _ = components as? [String: String] { if let value = components["ReadMore"] as? String, value == "1" { self.readMore(readMore: true) } if let value = components["ReadLess"] as? String, value == "1" { self.readLess(readLess: true) } } } }


Swift 4 y Swift 5 . Necesito implementar lo mismo. Como las respuestas ya están dadas, pero según mí TTTAttributedLabel es la mejor manera de hacerlo. Te da un mejor control sobre el contenido. Fácil de encontrar dirección, enlace, fecha, etc. También puede cambiar el color de los enlaces. El enlace TTTAttributedLabel Library ya se encuentra en la respuesta anterior. Vamos a la implementación.

viewcontroller.h @property (nonatomic,assign) BOOL isReadable; viewcontrollr.m #pragma mark :- Tap Gesture View All -(void)readMoreDidClickedGesture :(UITapGestureRecognizer *)objTapGesture{ UILabel * lblDesc = (UILabel *)[objTapGesture view]; NSLog(@"%@",lblDesc.text); if (self.isReadable == false) { [self setIsReadable:YES]; lblDesc.text = readmoreText; readMoreHeight = [self getLabelHeight:lblDesc]; } else{ readMoreHeight = 30.0; [self setIsReadable:NO]; } } - (void)addReadMoreStringToUILabel:(UILabel*)label isReaded:(BOOL)isReaded { NSString *readMoreText = (isReaded == false) ? @"...Show All " : @"Show Less "; NSInteger lengthForString = label.text.length; if (lengthForString >= 30) { NSInteger lengthForVisibleString = [self getLabelHeight:label];//[self fitString:label.text intoLabel:label]; NSMutableString *mutableString = [[NSMutableString alloc] initWithString:label.text]; readmoreText = mutableString; 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 :label.font, NSForegroundColorAttributeName :[UIColor orangeColor] }]; if (isReaded == false){ [readMoreAttributed addAttribute:NSUnderlineStyleAttributeName value:@(NSUnderlineStyleSingle) range:NSMakeRange(3, 8)]; NSTextAttachment *textAttachment = [[NSTextAttachment alloc] init]; UIImageView *imgDown = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 25, 25)]; imgDown.image = [UIImage imageNamed:@"searchFilterArrow1"]; imgDown.image = [imgDown.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; [imgDown setTintColor:[UIColor orangeColor]]; textAttachment.image = imgDown.image; NSAttributedString *attrStringWithImage = [NSAttributedString attributedStringWithAttachment:textAttachment]; [readMoreAttributed replaceCharactersInRange:NSMakeRange(12, 1) withAttributedString:attrStringWithImage]; } else{ [readMoreAttributed addAttribute:NSUnderlineStyleAttributeName value:@(NSUnderlineStyleSingle) range:NSMakeRange(1, 9)]; NSTextAttachment *textAttachment = [[NSTextAttachment alloc] init]; UIImageView *imgup = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 25, 25)]; imgup.image = [UIImage imageNamed:@"searchFilterArrow2"]; imgup.image = [imgup.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; [imgup setTintColor:[UIColor orangeColor]]; textAttachment.image = imgup.image; NSAttributedString *attrStringWithImage = [NSAttributedString attributedStringWithAttachment:textAttachment]; [readMoreAttributed replaceCharactersInRange:NSMakeRange(11, 1) withAttributedString:attrStringWithImage]; } [answerAttributed appendAttributedString:readMoreAttributed]; label.attributedText = answerAttributed; UITapGestureRecognizer *readMoreGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(readMoreDidClickedGesture:)]; readMoreGesture.numberOfTapsRequired = 1; [label addGestureRecognizer:readMoreGesture]; label.userInteractionEnabled = YES; } else { NSLog(@"No need for ''Read More''..."); } }

Aquí he creado una extensión de TTTAttributedLabel y puse aquí la lógica ReadMore y ReadLess. Puede modificar de acuerdo a su.

func updateData(_ label: UILabel) { self.headerLabel.text = detailViewModel.firstTitle self.detailLabel.text = detailViewModel.firstContent headerTitle = detailViewModel.firstTitle detailTitle = detailViewModel.firstContent DispatchQueue.main.async { let readMoreText = "...View More" let stringColor: UIColor = UIColor.blue let attributes = [NSForegroundColorAttributeName: stringColor] let numberOfLines = self.detailLabel.numberOfVisibleLines if numberOfLines > 2 { let lengthForVisibleString: Int = self.fit( self.detailLabel.text, into: self.detailLabel) let mutableString = self.detailLabel.text ?? "" let trimmedString = (mutableString as NSString).replacingCharacters(in: NSRange(location: lengthForVisibleString, length: (self.detailLabel?.text?.count ?? 0) - lengthForVisibleString), with: "") let readMoreLength: Int = readMoreText.count let trimmedForReadMore = (trimmedString as NSString).replacingCharacters(in: NSRange(location: trimmedString.count - readMoreLength, length: readMoreLength), with: "") let answerAttributed = NSMutableAttributedString(string: trimmedForReadMore, attributes: [NSFontAttributeName: self.detailLabel.font]) let readMoreAttributed = NSMutableAttributedString(string: readMoreText, attributes: attributes) answerAttributed.append(readMoreAttributed) self.detailLabel.attributedText = answerAttributed let readMoreGesture = UITapGestureRecognizer(target: self, action:#selector(FundDetailsTableViewCell.showViewMore(_:))) readMoreGesture.numberOfTapsRequired = 1 self.detailLabel.addGestureRecognizer(readMoreGesture) self.detailLabel.isUserInteractionEnabled = true } } } func fit(_ string: String?, into label: UILabel?) -> Int { guard let stringObjc = string as NSString? else { return 0 } let font: UIFont = label?.font ?? UIFont.systemFont(ofSize: 14.0) let mode: NSLineBreakMode? = label?.lineBreakMode let labelWidth: CGFloat? = label?.frame.size.width let labelHeight: CGFloat? = label?.frame.size.height let sizeConstraint = CGSize(width: labelWidth ?? 0.0, height: CGFloat.greatestFiniteMagnitude) let attributes = [NSFontAttributeName: font] let device = UIDevice.current let iosVersion = Double(device.systemVersion) ?? 0 if iosVersion > 7 { let attributedText = NSAttributedString(string: string ?? "", attributes: attributes) let boundingRect: CGRect = attributedText.boundingRect(with: sizeConstraint, options: .usesLineFragmentOrigin, context: nil) do { if boundingRect.size.height > (labelHeight ?? 0.0) { var index: Int = 0 var prev: Int let characterSet = CharacterSet.whitespacesAndNewlines repeat { prev = index if mode == .byCharWrapping { index += 1 } else { index = Int((string as NSString?)?.rangeOfCharacter(from: characterSet, options: [], range: NSRange(location: index + 1, length: (string?.count ?? 0) - index - 1)).location ?? 0) } } while index != NSNotFound && index < (string?.count ?? 0) && (stringObjc.substring(to: index).boundingRect(with: sizeConstraint, options: .usesLineFragmentOrigin, attributes: attributes, context: nil).size.height) <= labelHeight! return prev; } } } else { if stringObjc.size(attributes: attributes).height > labelHeight! { var index: Int = 0 var prev: Int let characterSet = CharacterSet.whitespacesAndNewlines repeat { prev = index if mode == .byCharWrapping { index += 1 } else { index = stringObjc.rangeOfCharacter(from: characterSet, options: NSString.CompareOptions.caseInsensitive, range: NSRange(location: index + 1, length: stringObjc.length - index - 1)).location } } while index != NSNotFound && index < (string?.count)! && (stringObjc.substring(to: index) as NSString).size(attributes: attributes).height <= labelHeight! return prev } } return (string?.count)! } func showViewMore(_ sender: UITapGestureRecognizer) { }

Debe implementar el delegado didSelectLinkWith TransitInformation de TTTAttributedLabel. Aquí puede obtener el componente que ha pasado

extension UILabel { var numberOfVisibleLines: Int { let textSize = CGSize(width: CGFloat(self.frame.size.width), height: CGFloat(MAXFLOAT)) let rHeight: Int = lroundf(Float(self.sizeThatFits(textSize).height)) let charSize: Int = lroundf(Float(self.font.pointSize)) return rHeight / charSize } }

Resultado: antes de tocar Leer más

Resultado: después de tocar Leer más


Swift4 (IOS 11.2)

Leer más al final de la etiqueta sin acción

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 } }

Uso

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) }

Resultado

NOTA: - La acción no está incluida para Readmore


let kCharacterBeforReadMore = 20 let kReadMoreText = "...ReadMore" let kReadLessText = "...ReadLess" @IBOutlet weak var labelText: TTTAttributedLabel! // setYouLabel Class to TTTAttributedLabel in StoryBoard var strFull = "" override func viewDidLoad() { super.viewDidLoad() strFull = "I need to implement the same. As answers are already given but according to me TTTAttributedLabel is the best way to do it. I gives I need to implement the same. As answers are already given but according to me TTTAttributedLabel is the best way to do it. I gives you" labelText.showTextOnTTTAttributeLable(str: strFull, readMoreText: kReadMoreText, readLessText: kReadLessText, font: UIFont.init(name: "Helvetica-Bold", size: 24.0)!, charatersBeforeReadMore: kCharacterBeforReadMore, activeLinkColor: UIColor.blue, isReadMoreTapped: false, isReadLessTapped: false) labelText.delegate = self } func readMore(readMore: Bool) { labelText.showTextOnTTTAttributeLable(str: strFull, readMoreText: kReadMoreText, readLessText: kReadLessText, font: nil, charatersBeforeReadMore: kCharacterBeforReadMore, activeLinkColor: UIColor.blue, isReadMoreTapped: readMore, isReadLessTapped: false) } func readLess(readLess: Bool) { labelText.showTextOnTTTAttributeLable(str: strFull, readMoreText: kReadMoreText, readLessText: kReadLessText, font: nil, charatersBeforeReadMore: kCharacterBeforReadMore, activeLinkColor: UIColor.blue, isReadMoreTapped: readLess, isReadLessTapped: true) } }

extension TTTAttributedLabel { func showTextOnTTTAttributeLable(str: String, readMoreText: String, readLessText: String, font: UIFont?, charatersBeforeReadMore: Int, activeLinkColor: UIColor, isReadMoreTapped: Bool, isReadLessTapped: Bool) { let text = str + readLessText let attributedFullText = NSMutableAttributedString.init(string: text) let rangeLess = NSString(string: text).range(of: readLessText, options: String.CompareOptions.caseInsensitive) //Swift 5 // attributedFullText.addAttributes([NSAttributedStringKey.foregroundColor : UIColor.blue], range: rangeLess) attributedFullText.addAttributes([NSAttributedString.Key.foregroundColor : UIColor.blue], range: rangeLess) var subStringWithReadMore = "" if text.count > charatersBeforeReadMore { let start = String.Index(encodedOffset: 0) let end = String.Index(encodedOffset: charatersBeforeReadMore) subStringWithReadMore = String(text[start..<end]) + readMoreText } let attributedLessText = NSMutableAttributedString.init(string: subStringWithReadMore) let nsRange = NSString(string: subStringWithReadMore).range(of: readMoreText, options: String.CompareOptions.caseInsensitive) //Swift 5 // attributedLessText.addAttributes([NSAttributedStringKey.foregroundColor : UIColor.blue], range: nsRange) attributedLessText.addAttributes([NSAttributedString.Key.foregroundColor : UIColor.blue], range: nsRange) if let _ = font { self.font = font } self.attributedText = attributedLessText self.activeLinkAttributes = [NSAttributedString.Key.foregroundColor : UIColor.blue] //Swift 5 // self.linkAttributes = [NSAttributedStringKey.foregroundColor : UIColor.blue] self.linkAttributes = [NSAttributedString.Key.foregroundColor : UIColor.blue] self.addLink(toTransitInformation: ["ReadMore":"1"], with: nsRange) if isReadMoreTapped { self.numberOfLines = 0 self.attributedText = attributedFullText self.addLink(toTransitInformation: ["ReadLess": "1"], with: rangeLess) } if isReadLessTapped { self.numberOfLines = 3 self.attributedText = attributedLessText } } }


class DynamicLabel: UILabel{ var fullText: String? var truncatedLength = 100 var isTruncated = true func collapse(){ let index = fullText!.index(fullText!.startIndex, offsetBy: truncatedLength) self.text = fullText![...index].description + "... More" isTruncated = true } func expand(){ self.text = fullText isTruncated = false } }

Solo un simple truco para superar todas estas implementaciones desordenadas. La idea es simple, no establecemos filas contraídas ni expandidas, solo establecemos la etiqueta en 0. Luego almacena el texto original en fullText variable. Ahora, si queremos mostrar el formato contraído, simplemente obtenga la subcadena y agregue los puntos suspensivos personalizados.

Nota: Esto no incluye controladores de eventos de tap, puede agregarlo usted mismo en el controlador.