s g
s g

Reputation: 5667

UIProgressView reveal progressImage image

I have a UIProgressView and I'm giving it a progressImage that I'd like to reveal as the progress bar progresses. How can I do this?

What currently happens is the whole images always fits inside the progress bar, it just gets compressed. I'd like to, instead, just show a partial (e.g. the left side) of the image.

Upvotes: 0

Views: 533

Answers (1)

DonMag
DonMag

Reputation: 77690

You'll need to create your own "custom progress view."

One approach is to use a CALayer as a mask on the image view, adjusting the size of the layer to be a percentage of the width of the custom view.

Here's a quick example...

Custom Progress View

class MyProgressView: UIView {
    // image that will be "revealed"
    public var image: UIImage? {
        didSet {
            bkgView.image = image
        }
    }
    public var progress: Float {
        set {
            // keep the value between 0.0 and 1.0
            _progress = max(min(newValue, 1.0), 0.0)
            setNeedsLayout()
        }
        get {
            return _progress
        }
    }
    private var _progress: Float = 0.0
    
    private let bkgView = UIImageView()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() -> Void {
        bkgView.translatesAutoresizingMaskIntoConstraints = false
        addSubview(bkgView)
        let g = self
        NSLayoutConstraint.activate([
            bkgView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
            bkgView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
            bkgView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
            bkgView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
        ])
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        // width to "reveal" will be the percentage of self's width
        let w: CGFloat = bounds.width * CGFloat(_progress)
        var r = bounds
        r.size.width = w
        
        // create a mask layer
        let msk = CALayer()
        // can be any color other than clear
        msk.backgroundColor = UIColor.black.cgColor
        msk.frame = r
        bkgView.layer.mask = msk
    }

}

Sample view controller

class MyProgessVC: UIViewController {

    let myProgressView = MyProgressView()
    let standardProgressView = UIProgressView()
    let infoLabel = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let g = view.safeAreaLayoutGuide

        // make sure we can load the image we want to use for our custom progress view
        guard let img = UIImage(named: "pvBKG") else { return }
        
        // add the standard progress view
        standardProgressView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(standardProgressView)

        // add our custom progress view
        myProgressView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(myProgressView)

        // add a slider to set the progress view percentage
        let slider = UISlider()
        slider.addTarget(self, action: #selector(sliderChanged(_:)), for: .valueChanged)
        
        slider.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(slider)
        
        // add a label to show the current progress
        infoLabel.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(infoLabel)
        infoLabel.textAlignment = .center
        
        NSLayoutConstraint.activate([

            // let's put the standard progress view near the top
            standardProgressView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
            // with 20-points on each side
            standardProgressView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            standardProgressView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
            
            // put our custom "progress view" below the standard one
            myProgressView.topAnchor.constraint(equalTo: standardProgressView.bottomAnchor, constant: 40.0),
            // with 20-points on each side
            myProgressView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            myProgressView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
            // we'll make the height equal to the image height
            myProgressView.heightAnchor.constraint(equalToConstant: img.size.height),

            // put the slider below the progress views
            slider.topAnchor.constraint(equalTo: myProgressView.bottomAnchor, constant: 40.0),
            slider.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            slider.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
            
            // put the info label below the slider
            infoLabel.topAnchor.constraint(equalTo: slider.bottomAnchor, constant: 40.0),
            infoLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            infoLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
            
        ])
        
        // color for progress view "right-side"
        myProgressView.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
        
        // set the "reveal" image
        myProgressView.image = img
        
        updateInfoLabel()
    }
    @objc func sliderChanged(_ sender: UISlider) {
        // set .progress on each to the slider value
        myProgressView.progress = sender.value
        standardProgressView.progress = sender.value
        updateInfoLabel()
    }
    func updateInfoLabel() {
        infoLabel.text = "\(myProgressView.progress)"
    }

}

We add a "standard" UIProgressView, an instance of our custom MyProgressView, a UISlider to interactively set the progress value, and a label to show the value.

Using this image for the progress view "reveal" image:

enter image description here

It looks like this when running:

enter image description here enter image description here

enter image description here enter image description here

If you want to emulate the animation capability of the default UIProgressView (as in calling .setProgress(0.75, animated: true)) you'll have a little more work to do :)

Upvotes: 1

Related Questions