繁体   English   中英

如何淡出多行标签中最后一行的结尾?

[英]How to fade out end of last line in multiline label?

请注意,它必须适用于 UILabel 中不同数量的行 - 1、2、3 等。我已经找到了 1 个行标签的解决方案,您可以在其中使用 CAGradientLayer 屏蔽 UILabel 的图层,但它不适用于多行标签,因为它掩盖了整个图层并淡出所有线条。

我尝试制作另一个 CALayer,其位置计算为具有所需宽度的最后一行的位置,并使用 CAGradientLayer 作为掩码并将该层添加为 UILabel 的子层,它适用于静态对象,但我在 UITableViewCell 和何时使用此 UILabel轻按 -它会将颜色更改为灰色,我可以看到我的图层,因为它在视图布局其子视图时使用 UILabel 的背景颜色,并且 x 位置计算也有问题:

extension UILabel {
    func fadeOutLastLineEnd() { //Call in layoutSubviews
        guard bounds.width > 0 else { return }

        lineBreakMode = .byCharWrapping
        let tmpLayer = CALayer()
        let gradientWidth: CGFloat = 32
        let numberOfLines = CGFloat(numberOfLines)
        tmpLayer.backgroundColor = UIColor.white.cgColor
        tmpLayer.frame = CGRect(x: layer.frame.width - gradientWidth,
                                y: layer.frame.height / numberOfLines,
                                width: gradientWidth,
                                height: layer.frame.height / numberOfLines)
        
        let tmpGrLayer = CAGradientLayer()

        tmpGrLayer.colors     = [UIColor.white.cgColor, UIColor.clear.cgColor]
        tmpGrLayer.startPoint = CGPoint(x: 1, y: 0)
        tmpGrLayer.endPoint   = CGPoint(x: 0, y: 0)
        tmpGrLayer.frame = tmpLayer.bounds
        
        tmpLayer.mask = tmpGrLayer
        layer.addSublayer(tmpLayer)
    }
}

所以,我需要UIL标签

  • 可以是多行的
  • 最后一行的结尾需要淡出(渐变?)
  • 当整个对象改变颜色时,在 UITableViewCell 中工作

您应该创建一个与您的UILabel具有相同文本属性的CATextLayer

用您希望淡入淡出的文本结尾填充它。

然后计算此文本段在您的UILabel中的位置。

最后将两者叠加。

这里有一些方面的解释。

有多种方法可以做到这一点——这是一种方法。

我们可以通过设置layer.mask来屏蔽视图。 遮罩的不透明区域将显示出来,而透明区域则不会。

所以,我们需要一个自定义层子类,看起来像这样:

在此处输入图像描述

这是一个我称之为InvertedGradientLayer的示例:

class InvertedGradientLayer: CALayer {
    
    public var lineHeight: CGFloat = 0
    public var gradWidth: CGFloat = 0
    
    override func draw(in inContext: CGContext) {
        
        // fill all but the bottom "line height" with opaque color
        inContext.setFillColor(UIColor.gray.cgColor)
        var r = self.bounds
        r.size.height -= lineHeight
        inContext.fill(r)

        // can be any color, we're going from Opaque to Clear
        let colors = [UIColor.gray.cgColor, UIColor.gray.withAlphaComponent(0.0).cgColor]
        
        let colorSpace = CGColorSpaceCreateDeviceRGB()
        
        let colorLocations: [CGFloat] = [0.0, 1.0]
        
        let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: colorLocations)!
        
        // start the gradient "grad width" from right edge
        let startPoint = CGPoint(x: bounds.maxX - gradWidth, y: 0.5)
        // end the gradient at the right edge, but
        // probably want to leave the farthest-right 1 or 2 points
        //  completely transparent
        let endPoint = CGPoint(x: bounds.maxX - 2.0, y: 0.5)

        // gradient rect starts at the bottom of the opaque rect
        r.origin.y = r.size.height - 1
        // gradient rect height can extend below the bounds, becuase it will be clipped
        r.size.height = bounds.height
        inContext.addRect(r)
        inContext.clip()
        inContext.drawLinearGradient(gradient, start: startPoint, end: endPoint, options: .drawsBeforeStartLocation)

    }
    
}

接下来,我们将创建一个UILabel子类,将InvertedGradientLayer实现为图层蒙版:

class CornerFadeLabel: UILabel {
    let ivgLayer = InvertedGradientLayer()
    override func layoutSubviews() {
        super.layoutSubviews()
        guard let f = self.font, let t = self.text else { return }
        // we only want to fade-out the last line if
        //  it would be clipped
        let constraintRect = CGSize(width: bounds.width, height: .greatestFiniteMagnitude)
        let boundingBox = t.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font : f], context: nil)
        if boundingBox.height <= bounds.height {
            layer.mask = nil
            return
        }
        layer.mask = ivgLayer
        ivgLayer.lineHeight = f.lineHeight
        ivgLayer.gradWidth = 60.0
        ivgLayer.frame = bounds
        ivgLayer.setNeedsDisplay()
    }
}

这是一个示例视图控制器,显示它正在使用中:

class FadeVC: UIViewController {
    
    let wordWrapFadeLabel: CornerFadeLabel = {
        let v = CornerFadeLabel()
        v.numberOfLines = 1
        v.lineBreakMode = .byWordWrapping
        return v
    }()
    
    let charWrapFadeLabel: CornerFadeLabel = {
        let v = CornerFadeLabel()
        v.numberOfLines = 1
        v.lineBreakMode = .byCharWrapping
        return v
    }()
    
    let normalLabel: UILabel = {
        let v = UILabel()
        v.numberOfLines = 1
        return v
    }()
    
    let numLinesLabel: UILabel = {
        let v = UILabel()
        v.textAlignment = .center
        return v
    }()
    
    var numLines: Int = 0
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .systemBackground
        
        let sampleText = "This is some example text that will wrap onto multiple lines and fade-out the bottom-right corner instead of truncating or clipping a last line."
        wordWrapFadeLabel.text = sampleText
        charWrapFadeLabel.text = sampleText
        normalLabel.text = sampleText
        
        let stack: UIStackView = {
            let v = UIStackView()
            v.axis = .vertical
            v.spacing = 8
            v.translatesAutoresizingMaskIntoConstraints = false
            return v
        }()
        
        let bStack: UIStackView = {
            let v = UIStackView()
            v.axis = .horizontal
            v.spacing = 8
            v.translatesAutoresizingMaskIntoConstraints = false
            return v
        }()
        
        let btnUP: UIButton = {
            let v = UIButton()
            let cfg = UIImage.SymbolConfiguration(pointSize: 28.0, weight: .bold, scale: .large)
            let img = UIImage(systemName: "chevron.up.circle.fill", withConfiguration: cfg)
            v.setImage(img, for: [])
            v.tintColor = .systemGreen
            v.widthAnchor.constraint(equalTo: v.heightAnchor).isActive = true
            v.addTarget(self, action: #selector(btnUpTapped), for: .touchUpInside)
            return v
        }()
        
        let btnDown: UIButton = {
            let v = UIButton()
            let cfg = UIImage.SymbolConfiguration(pointSize: 28.0, weight: .bold, scale: .large)
            let img = UIImage(systemName: "chevron.down.circle.fill", withConfiguration: cfg)
            v.setImage(img, for: [])
            v.tintColor = .systemGreen
            v.widthAnchor.constraint(equalTo: v.heightAnchor).isActive = true
            v.addTarget(self, action: #selector(btnDownTapped), for: .touchUpInside)
            return v
        }()
        
        bStack.addArrangedSubview(btnUP)
        bStack.addArrangedSubview(numLinesLabel)
        bStack.addArrangedSubview(btnDown)
        
        let v1 = UILabel()
        v1.text = "Word-wrapping"
        v1.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
        
        let v2 = UILabel()
        v2.text = "Character-wrapping"
        v2.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
        
        let v3 = UILabel()
        v3.text = "Normal Label (Truncate Tail)"
        v3.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
        
        stack.addArrangedSubview(bStack)
        stack.addArrangedSubview(v1)
        stack.addArrangedSubview(wordWrapFadeLabel)
        stack.addArrangedSubview(v2)
        stack.addArrangedSubview(charWrapFadeLabel)
        stack.addArrangedSubview(v3)
        stack.addArrangedSubview(normalLabel)

        stack.setCustomSpacing(20, after: bStack)
        stack.setCustomSpacing(20, after: wordWrapFadeLabel)
        stack.setCustomSpacing(20, after: charWrapFadeLabel)

        view.addSubview(stack)
        
        // dashed border views so we can see the lable frames
        let wordBorderView = DashedView()
        let charBorderView = DashedView()
        let normalBorderView = DashedView()
        wordBorderView.translatesAutoresizingMaskIntoConstraints = false
        charBorderView.translatesAutoresizingMaskIntoConstraints = false
        normalBorderView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(wordBorderView)
        view.addSubview(charBorderView)
        view.addSubview(normalBorderView)

        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            
            stack.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
            stack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 60.0),
            stack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -60.0),
            
            wordBorderView.topAnchor.constraint(equalTo: wordWrapFadeLabel.topAnchor, constant: 0.0),
            wordBorderView.leadingAnchor.constraint(equalTo: wordWrapFadeLabel.leadingAnchor, constant: 0.0),
            wordBorderView.trailingAnchor.constraint(equalTo: wordWrapFadeLabel.trailingAnchor, constant: 0.0),
            wordBorderView.bottomAnchor.constraint(equalTo: wordWrapFadeLabel.bottomAnchor, constant: 0.0),
            
            charBorderView.topAnchor.constraint(equalTo: charWrapFadeLabel.topAnchor, constant: 0.0),
            charBorderView.leadingAnchor.constraint(equalTo: charWrapFadeLabel.leadingAnchor, constant: 0.0),
            charBorderView.trailingAnchor.constraint(equalTo: charWrapFadeLabel.trailingAnchor, constant: 0.0),
            charBorderView.bottomAnchor.constraint(equalTo: charWrapFadeLabel.bottomAnchor, constant: 0.0),
            
            normalBorderView.topAnchor.constraint(equalTo: normalLabel.topAnchor, constant: 0.0),
            normalBorderView.leadingAnchor.constraint(equalTo: normalLabel.leadingAnchor, constant: 0.0),
            normalBorderView.trailingAnchor.constraint(equalTo: normalLabel.trailingAnchor, constant: 0.0),
            normalBorderView.bottomAnchor.constraint(equalTo: normalLabel.bottomAnchor, constant: 0.0),
            
        ])
        
        // set initial number of lines to 1
        btnUpTapped()
        
    }
    @objc func btnUpTapped() {
        numLines += 1
        numLinesLabel.text = "Num Lines: \(numLines)"
        wordWrapFadeLabel.numberOfLines = numLines
        charWrapFadeLabel.numberOfLines = numLines
        normalLabel.numberOfLines = numLines
    }
    @objc func btnDownTapped() {
        if numLines == 1 { return }
        numLines -= 1
        numLinesLabel.text = "Num Lines: \(numLines)"
        wordWrapFadeLabel.numberOfLines = numLines
        charWrapFadeLabel.numberOfLines = numLines
        normalLabel.numberOfLines = numLines
    }
}

运行时,它看起来像这样:

在此处输入图像描述

红色虚线边框在那里,我们可以看到标签的框架。 点击向上/向下箭头将增加/减少每个标签中显示的最大行数。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM