3

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

David
  • 149
  • 1
  • 9
  • Does this answer your question? [Fade the end of a label text](https://stackoverflow.com/questions/24365330/fade-the-end-of-a-label-text) – Fattie Feb 13 '23 at 20:20

2 Answers2

1

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.

Shawn Frank
  • 4,381
  • 2
  • 19
  • 29
  • doesn't work for me, the fading label doesn't shows up `let playlistNameLabel: FadingLabel = { let label = FadingLabel() label.text = "\(playlist.name)" label.textColor = .white label.numberOfLines = 1 return label }()` ------------- `playlistView.addSubview(playlistNameLabel) playlistNameLabel.anchor(top: playlistView.topAnchor, left: playlistImage.rightAnchor, right: playlistView.rightAnchor, paddingTop: 7.5, paddingLeft: 7.5, paddingRight: -7.5)` – David Jan 23 '22 at 11:54
  • 1
    It seems there was a bug because sometimes the frame is not yet set of the label depends on when you add the label to the subview. I have updated the above code by changing `if intrinsicContentSize.width > bounds.width` with `if bounds.width > CGFloat.zero && intrinsicContentSize.width > bounds.width` and I believe it should fix it. I have updated the repo as well. Have a look and report back if it still doesn't help. I'll be happy to help you resolve this. – Shawn Frank Jan 23 '22 at 12:40
  • 1
    This didn't fixed it fully for me but I played around with the variables and it works almost perfect now I've changed the start point from x: 0.8 to 0.7 and the endpoint from x: 0.95 to 0.85 also I changed `label.allowsDefaultTighteningForTruncation = true` this setting to true but one question is there a way to remove the "..." as truncate to make it more kinda "seamless looking" and don't cutting it away so much of the end? Thanks for your help – David Jan 23 '22 at 15:43
  • 1
    @Bumo - agreed, you need to play around with the gradient start points. I searched a lot to even solve the default ... - no luck till now. The only thing is to start the gradient earlier. However, if I find a solution I will update this answer and you - I will keep looking ! – Shawn Frank Jan 23 '22 at 16:28
  • 1
    @Bumo - I added an update to remove the three dots - have a look and check if this solves your issue. – Shawn Frank Jan 25 '22 at 04:50
  • Thank you very much this solved the issue – David Feb 03 '22 at 14:14
  • unfortunately this has many, many problems. it's much easier than this. – Fattie Feb 13 '23 at 20:20
  • honestly guys there are so many problems here. https://stackoverflow.com/a/75440774/294884 its that easy to do it perfectly – Fattie Feb 13 '23 at 20:21
-1

I Think the easiest way is to use the following code:

titleLabel.adjustsFontSizeToFitWidth = false
titleLabel.lineBreakMode = .byClipping

It automatically fades the last words if applicable and text size has more width than UILabel!

thanks to thi

Majid
  • 1
  • 1