在 UIImageView 上添加部分遮罩

Adding a partial mask over an UIImageView

我想在图像的一部分上添加 0.5 alpha 掩码(我将在代码中计算)。 基本上,它是一个 5 星评级控件,但星星不是一种颜色,而是一些像这样的漂亮图片:

我需要尊重图片的透明背景。因此,我希望能够添加遮罩或以某种方式设置图像的一半的 alpha,例如,当您的评级为 3.5 时。 (2 颗满星和一颗有一半的 alpha 较少)

我不能只在上面放一个 0.5 alpha 的 UIView,因为这也会影响显示星星的背景。

有什么想法吗?

您可以使用 CAGradientLayer 作为掩码:

    gLayer.startPoint = CGPoint.zero
    gLayer.endPoint = CGPoint(x: 1.0, y: 0.0)
    gLayer.locations = [
        0.0, 0.5, 0.5, 1.0,
    ]
    gLayer.colors = [
        UIColor.black.cgColor,
        UIColor.black.cgColor,
        UIColor.black.withAlphaComponent(0.5).cgColor,
        UIColor.black.withAlphaComponent(0.5).cgColor,
    ]

这将创建一个水平渐变,左半部分为全 alpha,右半部分为 50% alpha。

因此,以此作为掩码的白色视图将如下所示:

如果我们将图片设置为您的明星,它看起来像这样:

如果我们希望星星“填充 75%”,我们更改位置:

    gLayer.locations = [
        0.0, 0.75, 0.75, 1.0,
    ]

导致:

这是“五星级”评级视图的示例实现:

@IBDesignable
class FiveStarRatingView: UIView {
    
    @IBInspectable
    public var rating: CGFloat = 0.0 {
        didSet {
            var r = rating
            stack.arrangedSubviews.forEach {
                if let v = [=12=] as? PercentImageView {
                    v.percent = min(1.0, r)
                    r -= 1.0
                }
            }
        }
    }
    
    @IBInspectable
    public var ratingImage: UIImage = UIImage() {
        didSet {
            stack.arrangedSubviews.forEach {
                if let v = [=12=] as? PercentImageView {
                    v.image = ratingImage
                }
            }
        }
    }
    
    @IBInspectable
    public var tranparency: CGFloat = 0.5 {
        didSet {
            stack.arrangedSubviews.forEach {
                if let v = [=12=] as? PercentImageView {
                    v.tranparency = tranparency
                }
            }
        }
    }
    
    override var intrinsicContentSize: CGSize {
        return CGSize(width: 100.0, height: 20.0)
    }
    
    private let stack: UIStackView = {
        let v = UIStackView()
        v.axis = .horizontal
        v.alignment = .center
        v.distribution = .fillEqually
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    private func commonInit() -> Void {
        addSubview(stack)
        // constrain stack view to all 4 sides
        NSLayoutConstraint.activate([
            stack.topAnchor.constraint(equalTo: topAnchor),
            stack.leadingAnchor.constraint(equalTo: leadingAnchor),
            stack.trailingAnchor.constraint(equalTo: trailingAnchor),
            stack.bottomAnchor.constraint(equalTo: bottomAnchor),
        ])
        // add 5 Percent Image Views to the stack view
        for _ in 1...5 {
            let v = PercentImageView(frame: .zero)
            stack.addArrangedSubview(v)
            v.heightAnchor.constraint(equalTo: v.widthAnchor).isActive = true
        }
    }
    
    private class PercentImageView: UIImageView {
        
        var percent: CGFloat = 0.0 {
            didSet {
                setNeedsLayout()
            }
        }
        
        var tranparency: CGFloat = 0.5 {
            didSet {
                setNeedsLayout()
            }
        }
        
        private let gLayer = CAGradientLayer()
        
        override init(frame: CGRect) {
            super.init(frame: frame)
            commonInit()
        }
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            commonInit()
        }
        func commonInit() -> Void {
            gLayer.startPoint = CGPoint.zero
            gLayer.endPoint = CGPoint(x: 1.0, y: 0.0)
            layer.mask = gLayer
        }
        override func layoutSubviews() {
            super.layoutSubviews()

            // we don't want the layer's intrinsic animation
            CATransaction.begin()
            CATransaction.setDisableActions(true)

            gLayer.frame = bounds
            gLayer.locations = [
                0.0, percent as NSNumber, percent as NSNumber, 1.0,
            ]
            gLayer.colors = [
                UIColor.black.cgColor,
                UIColor.black.cgColor,
                UIColor.black.withAlphaComponent(tranparency).cgColor,
                UIColor.black.withAlphaComponent(tranparency).cgColor,
            ]
            
            CATransaction.commit()
        }
    }

}


class StarRatingViewController: UIViewController {

    let ratingView = FiveStarRatingView()
    
    let slider = UISlider()
    let valueLabel = UILabel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        guard let starImage = UIImage(named: "star") else {
            fatalError("Could not load image named \"star\"")
        }
        
        // add a slider and a couple labels so we can change the rating
        let minLabel = UILabel()
        let maxLabel = UILabel()
        [slider, valueLabel, minLabel, maxLabel].forEach {
            view.addSubview([=12=])
            [=12=].translatesAutoresizingMaskIntoConstraints = false
            if let v = [=12=] as? UILabel {
                v.textAlignment = .center
            }
        }
        
        let g = view.safeAreaLayoutGuide
        
        NSLayoutConstraint.activate([
            valueLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
            valueLabel.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            
            slider.topAnchor.constraint(equalTo: valueLabel.bottomAnchor, constant: 8.0),
            slider.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 32.0),
            slider.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -32.0),
            
            minLabel.topAnchor.constraint(equalTo: slider.bottomAnchor, constant: 8.0),
            minLabel.centerXAnchor.constraint(equalTo: slider.leadingAnchor, constant: 0.0),
            
            maxLabel.topAnchor.constraint(equalTo: slider.bottomAnchor, constant: 8.0),
            maxLabel.centerXAnchor.constraint(equalTo: slider.trailingAnchor, constant: 0.0),
        ])
        minLabel.text = "0"
        maxLabel.text = "5"
        
        ratingView.translatesAutoresizingMaskIntoConstraints = false
        
        view.addSubview(ratingView)

        NSLayoutConstraint.activate([
            // constrain the rating view centered in the view
            //  300-pts wide
            //  height will be auto-set by the rating view
            ratingView.topAnchor.constraint(equalTo: minLabel.bottomAnchor, constant: 20.0),
            ratingView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            ratingView.widthAnchor.constraint(equalToConstant: 240.0),
        ])
        
        // use the star image
        ratingView.ratingImage = starImage
        
        // start at rating of 0 stars
        updateValue(0.0)
        slider.value = 0
        
        slider.addTarget(self, action: #selector(self.sliderChanged(_:)), for: .valueChanged)
    }
    
    @objc func sliderChanged(_ sender: UISlider) {
        // round the slider value to 2 decimal places
        updateValue((sender.value * 5.0).rounded(digits: 2))
    }
    
    func updateValue(_ v: Float) -> Void {
        valueLabel.text = String(format: "%.2f", v)
        ratingView.rating = CGFloat(v)
    }
    
}

extension Float {
    func rounded(digits: Int) -> Float {
        let multiplier = Float(pow(10.0, Double(digits)))
        return (self * multiplier).rounded() / multiplier
    }
}

结果:

请注意,FiveStarRatingView class 已标记为 @IBDesignable,因此您可以将其添加到 Storyboard / IB 中,并在设计时设置图像、透明度和等级。