How do I Fade Label out at end instead of replacing end with "..." if it's too long / How to use GoogleToolboxForMac

Viewed 199

I've found a solution with GTMFadeTruncatingLabelTest from GoogleToolboxForMac but don't really understand how to use it and I don't find anything about it

but if you have another solution besides of this one

If you can't help me with GoogleToolboxForMac feel free to suggest other solution

Should kinda look like this at the end:

image

1 Answers

I am not sure about the GTMFadeTruncatingLabelTest but I can offer an alternative solution.

Steps

  1. Check if the label's text is going to be truncated
  2. If 1 is true, Create a CAGradientLayer that goes from Opaque to Transparent
  3. Apply the gradient layer as a mask to the UILabel

Implementation

If you don't want to read the rest, just grab the code from this repo

I wrapped step 1, 2 and 3 from above in a custom UILabel subclass. The reasoning is explained in the comments.

class FadingLabel: UILabel
{
    // Add a property observer for text changes
    // as we might not need to fade anymore
    override var text: String?
    {
        didSet
        {
            // Check if the text needs to be faded
            fadeTailIfRequired()
        }
    }
    
    // Add a property observer for numberOfLines changes
    // as only 1 line labels are supported for now
    override var numberOfLines: Int
    {
        didSet
        {
            // Reset the number of lines to 1
            if numberOfLines != 1
            {
                numberOfLines = 1
            }
        }
    }
    
    override func layoutSubviews()
    {
        super.layoutSubviews()
        
        // The label's frame might have changed so check
        // if the text needs to be faded or not
        fadeTailIfRequired()
    }
    
    
    /// The function that handles fading the tail end of the text if the text goes
    /// beyond the bounds of the label's width
    private func fadeTailIfRequired()
    {
        // Reset the numberOfLines to 1
        numberOfLines = 1
        
        // Prevent processing fading when the library is in the middle of
        // processing the string to truncate the ellipsis
        if !isTruncatingEllipsis
        {
            // Check if the label's has it's width set and if the text goes
            // beyond it's width plus a margin of safety
            if bounds.width > CGFloat.zero && intrinsicContentSize.width > bounds.width + 5
            {
                // Fade label works better with this setting
                allowsDefaultTighteningForTruncation = true
                
                // Initialize and configure a gradient to start at the end of
                // the label
                let gradient = CAGradientLayer()
                gradient.frame = bounds
                gradient.colors = [UIColor.white.cgColor, UIColor.clear.cgColor]
                gradient.startPoint = CGPoint(x: 0.8, y: 0.5)
                gradient.endPoint = CGPoint(x: 0.99, y: 0.5)
                
                // Apply the gradient as a mask to the UILabel
                layer.mask = gradient
                
                // Remove ellipsis added as the default UILabel truncation character
                removeEllipsis()
                
                // We do not need to go beyond this point
                return
            }
            
            // If the text has not been truncated, remove the gradient mask
            if originalText == text
            {
                // Remove the layer mask
                layer.mask = nil
            }
        }
    }
    
    /// Keep removing 1 character from the label till it no longer needs to truncate
    private func removeEllipsis()
    {
        isTruncatingEllipsis = true
        
        // Keep looping till we do not have the perfect string length
        // to fit into the label
        while intrinsicContentSize.width > bounds.width
        {
            // Drop the last character
            text = String(text!.dropLast())
        }
        
        isTruncatingEllipsis = false
    }
}

Then you can use it like a regular UILabel, for example:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    
    let fadingLabelWithLongText = FadingLabel()
    view.addSubview(fadingLabelWithLongText)
    fadingLabelWithLongText.text = "Fading label with text more than it's bounds can handle"
    fadingLabelWithLongText.textColor = .white
    fadingLabelWithLongText.frame = CGRect(x: 20, y: 90, width: 250, height: 50)
    
    let regularLabelWithLongText = UILabel()
    view.addSubview(regularLabelWithLongText)
    regularLabelWithLongText.text = "Regular label with text more than it's bounds can handle"
    regularLabelWithLongText.textColor = .white
    regularLabelWithLongText.frame = CGRect(x: 20, y: 160, width: 250, height: 50)
    
    let fadingLabelWithShortText = UILabel()
    view.addSubview(fadingLabelWithShortText)
    fadingLabelWithShortText.text = "Fading label with text that fits"
    fadingLabelWithShortText.textColor = .white
    fadingLabelWithShortText.frame = CGRect(x: 20, y: 230, width: 250, height: 50)
    
    let regularLabelWithShortText = UILabel()
    view.addSubview(regularLabelWithShortText)
    regularLabelWithShortText.text = "Regular label with text that fits"
    regularLabelWithShortText.textColor = .white
    regularLabelWithShortText.frame = CGRect(x: 20, y: 300, width: 250, height: 50)
}

Output

Custom UILabel with fade truncated text

Limitation

This way only supports single line UILabels

Update

Added a function to remove the default truncation method of using ellipsis (three dots) by UILabel with this function.

/// Keep removing 1 character from the label till it no longer needs to truncate
private func removeEllipsis()
{
    isTruncatingEllipsis = true
    
    // Keep looping till we do not have the perfect string length
    // to fit into the label
    while intrinsicContentSize.width > bounds.width
    {
        // Drop the last character
        text = String(text!.dropLast())
    }
    
    isTruncatingEllipsis = false
}

This function has been updated in the original code and repo mentioned above.

Related