问题
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!
回答1:
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
回答2:
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!
回答3:
Tttattributed label has this feature
https://github.com/TTTAttributedLabel/TTTAttributedLabel
You need to set the "truncation" token as "read more..."
See
attributedTruncationToken
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
回答4:
This works for Swift 4.2
Here is a safer version of @ramchandran's answer because you don’t know how many characters the user will enter.
In his answer if the length of the string the user entered is less then the length of the whatever text you decide to use for ... Readmore
then it will crash. For eg this is how you use it
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)
}
}
In the above example the output of ... Readmore
is 12 characters total. If the string the user entered was yourLabel.text = "12345678"
then the string's text would only be 8 characters.
It would crash because the range using ((trimmedString?.count ?? 0) - readMoreLength)
in the line below would produce a negative result:
// “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
I added a safety check to make sure that if the string entered is less then or equal to the number of characters for whatever you decide to use as ... Readmore
it will return and the line that will cause the crash will never get reached:
// trimmedString is the string the user entered
guard let safeTrimmedString = trimmedString else { return }
if safeTrimmedString.count <= readMoreLength { return }
It's located in the center of the addTrailing
function
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
}
}
}
回答5:
You can try the 3rd library ExpandableLable
Set the custom class of your UILabel to ExpandableLabel and set the desired number of lines and collapsed text:
expandableLabel.numberOfLines = 5
expandableLabel.collapsedAttributedLink = NSAttributedString(string: "more")
expandableLabel.ellipsis = NSAttributedString(string: "...")
// update label expand or collapse state
expandableLabel.collapsed = true
You may need set a delegate
to get notified in case the link has been touched.
回答6:
My solution is, I create a UIButton
(name Read more) at bottom-right and below the UILabel
.
After that I check the UILabel
is truncated or not for showing or hiding the 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;
}
=== Swift 3 version
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
}
add this extension:
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
}
}
Hope this help
回答7:
Swift 4 and Swift 5. I need to implement the same. As answers are already given but according to me TTTAttributedLabel is the best way to do it. It gives you better control over content. Easy to find address, link, date etc. You can also change the color of links. TTTAttributedLabel Library link is already given in above answer. Lets come to implementation.
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)
}
}
Here I have crated an extension of TTTAttributedLabel and put the ReadMore and ReadLess logic here. You can modify according to your.
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 {// set font to attributes
// 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
}
}
}
You need to implement the didSelectLinkWithTransitInformation delegate of TTTAttributedLabel. Here you can get the component which you have passed
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)
}
}
}
}
Result- Before tapping ReadMore
Result- After tapping ReadMore
回答8:
Using method - boundingRectWithSize:options:attributes:context: and passing your font as NSFontAttributeName
key for NSAttributedString
will give you the correct rect needed.
From that you need to check if it's bigger than your label bounds minus offset. Only if yes, you need to trim your text and show Read More
at the end.
回答9:
this method is useful for showless and showAll with updown arrow image: add tapgesture on label
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'...");
}
}
回答10:
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) {
}
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
}
}
回答11:
For Action
let tap = UITapGestureRecognizer(target: self, action: #selector(self.tapFunction))
Urlabel.isUserInteractionEnabled = true
Urlabel.addGestureRecognizer(tap)
@objc
func tapFunction(sender:UITapGestureRecognizer) {
}
回答12:
For Action on the label, if using a CollectionView or TableView you could use a delegate method to perform the action.
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()
}
This updates the label and displays the full text as required Using Lance Samaria answer and adding the action for the cell.
回答13:
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
}
}
Just a simple trick to get through all these messy implementations. The idea is simple, we don't set collapsed or expand rows, just set the label to 0.
Then store the original text on fullText
variable. Now if we want to display collapsed format, then just get substring and add the custom ellipsis.
Note: This does not include tap event handlers, you can add it yourself on the controller.
回答14:
Do you know there is no touch action of UILabel. so you cant touch '...Read More' if whole text in a UILabel.
Note: my solution is, add a clear background button end of the UILabel.
来源:https://stackoverflow.com/questions/32309247/add-read-more-to-the-end-of-uilabel