Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Check for Truncation in UILabel - iOS, Swift

I'm working on an app in swift. Currently, I'm working on the population of a table view with custom cells, see screenshot. However, right now I have the text set so that the title is exactly 2 lines and the summary is exactly 3 lines. By doing this, the text is sometimes truncated. Now, I want to set the priority for text in the title, so that if the title is truncated when it is 2 lines long I expand it to 3 lines and make the summary only 2 lines. I tried doing this with auto layout, but failed. Now I tried the following approach, according to this and this, but the function below also didn't seem to accurately determine if the text is truncated.

    func isTruncated(label:UILabel) -> Bool {
    let context = NSStringDrawingContext()
    let text : NSAttributedString = NSAttributedString(string: label.text!, attributes: [NSFontAttributeName : label.font])

    let labelSize : CGSize = CGSize(width: label.frame.width, height: CGFloat.max)


    let options : NSStringDrawingOptions = unsafeBitCast(NSStringDrawingOptions.UsesLineFragmentOrigin.rawValue | NSStringDrawingOptions.UsesFontLeading.rawValue, NSStringDrawingOptions.self)

    let labelRect : CGRect = text.boundingRectWithSize(labelSize, options: options, context: context)

    if Float(labelRect.height/label.font.lineHeight) > Float(label.numberOfLines) {
        return true
    } else {
        return false
    }
}

Can anybody help? How can I change my function to make this work? Or should work with different auto layout constraints and how? Thanks so much!


EDIT: this is my current code. Some auto layout is done is the storyboard, however the changing auto layout is done in code. import UIKit

class FeedTableViewCell: UITableViewCell {

var thumbnailImage = UIImageView()

@IBOutlet var titleText: UILabel!

@IBOutlet var summaryText: UILabel!

@IBOutlet var sourceAndDateText: UILabel!

var imgTitleConst = NSLayoutConstraint()
var imgSummaryConst = NSLayoutConstraint()
var imgDetailConst = NSLayoutConstraint()

var titleConst = NSLayoutConstraint()
var summaryConst = NSLayoutConstraint()
var detailConst = NSLayoutConstraint()

var titleHeightConst = NSLayoutConstraint()
var summaryHeightConst = NSLayoutConstraint()


var imageRemoved = false
var titleConstAdd = false
override func awakeFromNib() {
    super.awakeFromNib()
    thumbnailImage.clipsToBounds = true
    summaryText.clipsToBounds = true
    titleText.clipsToBounds = true
    sourceAndDateText.clipsToBounds = true
    addImage()

}

   func removeImage() {
    if let viewToRemove = self.viewWithTag(123) {
        imageRemoved = true
         viewToRemove.removeFromSuperview()
          self.contentView.removeConstraints([imgTitleConst,  imgSummaryConst, imgDetailConst])
        titleConst = NSLayoutConstraint(item: self.titleText, attribute: NSLayoutAttribute.Left, relatedBy: NSLayoutRelation.Equal, toItem: self.contentView, attribute: NSLayoutAttribute.Left, multiplier: 1, constant: 14)

        summaryConst = NSLayoutConstraint(item: summaryText, attribute: NSLayoutAttribute.Left, relatedBy: NSLayoutRelation.Equal, toItem: self.contentView, attribute: NSLayoutAttribute.Left, multiplier: 1, constant: 14)

        detailConst = NSLayoutConstraint(item: sourceAndDateText, attribute: NSLayoutAttribute.Left, relatedBy: NSLayoutRelation.Equal, toItem: self.contentView, attribute: NSLayoutAttribute.Left, multiplier: 1, constant: 14)

        self.contentView.addConstraints([titleConst, detailConst, summaryConst])
        setNumberOfLines()
        self.contentView.layoutSubviews()
    }


}

func addImage() {
    thumbnailImage.tag = 123
    thumbnailImage.image = UIImage(named: "placeholder")
    thumbnailImage.frame = CGRectMake(14, 12, 100, 100)
    thumbnailImage.contentMode = UIViewContentMode.ScaleAspectFill
    thumbnailImage.clipsToBounds = true
    self.contentView.addSubview(thumbnailImage)

    if imageRemoved {
        self.contentView.removeConstraints([titleConst, summaryConst, detailConst])
    }


    var widthConst = NSLayoutConstraint(item: thumbnailImage, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: 100)
    var heightConst = NSLayoutConstraint(item: thumbnailImage, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: 100)
    var leftConst = NSLayoutConstraint(item: thumbnailImage, attribute: NSLayoutAttribute.Left, relatedBy: NSLayoutRelation.Equal, toItem: self.contentView, attribute: NSLayoutAttribute.Left, multiplier: 1, constant: 14)
    var topConst = NSLayoutConstraint(item: thumbnailImage, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: self.contentView, attribute: NSLayoutAttribute.Top, multiplier: 1, constant: 12)



      imgTitleConst = NSLayoutConstraint(item: self.titleText, attribute: NSLayoutAttribute.Left, relatedBy: NSLayoutRelation.Equal, toItem: self.thumbnailImage, attribute: NSLayoutAttribute.Right, multiplier: 1, constant: 8)

     imgSummaryConst = NSLayoutConstraint(item: summaryText, attribute: NSLayoutAttribute.Left, relatedBy: NSLayoutRelation.Equal, toItem: self.thumbnailImage, attribute: NSLayoutAttribute.Right, multiplier: 1, constant: 8)

     imgDetailConst = NSLayoutConstraint(item: sourceAndDateText, attribute: NSLayoutAttribute.Left, relatedBy: NSLayoutRelation.Equal, toItem: self.thumbnailImage, attribute: NSLayoutAttribute.Right, multiplier: 1, constant: 8)


    self.contentView.addConstraints([widthConst, heightConst, leftConst, topConst, imgTitleConst, imgSummaryConst, imgDetailConst])
    setNumberOfLines()
    self.contentView.layoutSubviews()

}




override func setSelected(selected: Bool, animated: Bool) {
    super.setSelected(selected, animated: animated)

    // Configure the view for the selected state
}

func setNumberOfLines() {

    if titleConstAdd {
        self.contentView.removeConstraints([titleHeightConst, summaryHeightConst])
    }
    if titleText.numberOfLines == 3 {
        titleText.numberOfLines = 2
    }
    if countLabelLines(titleText) > 2 {
        titleText.numberOfLines = 3
        summaryText.numberOfLines = 2
        println("adjusting label heigh to be taller")
         titleHeightConst = NSLayoutConstraint(item: titleText, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: 51)
        summaryHeightConst = NSLayoutConstraint(item: summaryText, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: 32)
        self.contentView.addConstraints([titleHeightConst, summaryHeightConst])
    } else {
        titleText.numberOfLines = 2
        summaryText.numberOfLines = 3

         titleHeightConst = NSLayoutConstraint(item: titleText, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: 36)
            summaryHeightConst = NSLayoutConstraint(item: summaryText, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: 47)
        self.contentView.addConstraints([titleHeightConst, summaryHeightConst])
    }

    titleConstAdd = true


 }
 }


func countLabelLines(label:UILabel)->Int{

if let text = label.text{
    // cast text to NSString so we can use sizeWithAttributes
    var myText = text as NSString

    //Set attributes
    var attributes = [NSFontAttributeName :  UIFont.boldSystemFontOfSize(14)]

    //Calculate the size of your UILabel by using the systemfont and the paragraph we created before. Edit the font and replace it with yours if you use another

    var labelSize = myText.boundingRectWithSize(CGSizeMake(label.bounds.width, CGFloat.max), options: NSStringDrawingOptions.UsesLineFragmentOrigin, attributes: attributes, context: nil)

    //Now we return the amount of lines using the ceil method
    var lines = ceil(CGFloat(labelSize.height) / label.font.lineHeight)
    println(labelSize.height)
    println("\(lines)")
    return Int(lines)
}

return 0

}
like image 946
The Hexagon Avatar asked Mar 07 '15 22:03

The Hexagon


3 Answers

You can use the sizeWithAttributes method from NSString to get the number of lines your UILabel has. You will have to cast your label text to NSString first to use this method:

func countLabelLines(label:UILabel)->Int{

    if let text = label.text{
        // cast text to NSString so we can use sizeWithAttributes
        var myText = text as NSString
        //A Paragraph that we use to set the lineBreakMode.
        var paragraph = NSMutableParagraphStyle()
        //Set the lineBreakMode to wordWrapping
        paragraph.lineBreakMode = NSLineBreakMode.ByWordWrapping

        //Calculate the size of your UILabel by using the systemfont and the paragraph we created before. Edit the font and replace it with yours if you use another
        var labelSize = myText.sizeWithAttributes([NSFontAttributeName : UIFont.systemFontOfSize(17), NSParagraphStyleAttributeName : paragraph.copy()])

        //Now we return the amount of lines using the ceil method
        var lines = ceil(CGFloat(size.height) / label.font.lineHeight)
        return Int(lines)
    }

    return 0

}

Edit

If this method doesn't work for you because your label hasn't a fixed width, you can use boundingRectWithSize to get the size of the label and use that with the ceil method.

func countLabelLines(label:UILabel)->Int{

    if let text = label.text{
        // cast text to NSString so we can use sizeWithAttributes
        var myText = text as NSString

        //Set attributes
        var attributes = [NSFontAttributeName : UIFont.systemFontOfSize(UIFont.systemFontSize())]

        //Calculate the size of your UILabel by using the systemfont and the paragraph we created before. Edit the font and replace it with yours if you use another
        var labelSize = myText.boundingRectWithSize(CGSizeMake(label.bounds.width, CGFloat.max), options: NSStringDrawingOptions.UsesLineFragmentOrigin, attributes: attributes, context: nil)

        //Now we return the amount of lines using the ceil method
        var lines = ceil(CGFloat(labelSize.height) / label.font.lineHeight)
        return Int(lines)
    }

    return 0

}
like image 53
Christian Avatar answered Oct 12 '22 11:10

Christian


Swift 3

A simple solution is counting the number of lines after assigning the string and compare to the max number of lines of the label.

import Foundation
import UIKit

extension UILabel {

    func countLabelLines() -> Int {
        // Call self.layoutIfNeeded() if your view is uses auto layout
        let myText = self.text! as NSString
        let attributes = [NSFontAttributeName : self.font]

        let labelSize = myText.boundingRect(with: CGSize(width: self.bounds.width, height: CGFloat.greatestFiniteMagnitude), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: attributes, context: nil)
        return Int(ceil(CGFloat(labelSize.height) / self.font.lineHeight))
    }

    func isTruncated() -> Bool {

        if (self.countLabelLines() > self.numberOfLines) {
            return true
        }
        return false
    }
}
like image 28
Claus Avatar answered Oct 12 '22 11:10

Claus


This works for labels with fixed width and fixed number of lines or fixed height:

extension UILabel {
    func willBeTruncated() -> Bool {
        let label:UILabel = UILabel(frame: CGRectMake(0, 0, self.bounds.width, CGFloat.max))
        label.numberOfLines = 0
        label.lineBreakMode = NSLineBreakMode.ByWordWrapping
        label.font = self.font
        label.text = self.text
        label.sizeToFit()
        if label.frame.height > self.frame.height {
            return true
        }
        return false
    }
}
like image 41
guido Avatar answered Oct 12 '22 11:10

guido