2

I have a progress view like the one Snapchat and Instagram Stories have. My content changes after the progress view reaches to the end or when tapped on a button.

I'm reseting the progress view when content changes. Everything works as expected while there is no intervention. But when tapped on next button the progress view doesn't start again until the other loop executes.

You can see the video here quickly.

I came across this question while I was researching, I have the same scenario but I couldn't apply the principle as a newbie with swift 3.

Here is my code, any help would be highly appreciated:

func startTimer(){
    self.timer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(myVievController.nextItem), userInfo: nil, repeats: false)
}

func updateProgressBar() {
    self.progressView.setProgress(1.0, animated: false)
    UIView.animate(withDuration: 2.8, animations: {() -> Void in
        self.progressView.layoutIfNeeded()
    }, completion: { finished in
        if finished {
            self.progressView.setProgress(0, animated: false)
        }
    })
}

func nextItem(){
    // ...
    self.updateUI(item: self.myCurrentItem)
    // ...
}

func updateUI(item:MyItem){
    self.updateProgressBar()
    self.timer.invalidate()
    self.startTimer()

    // Clear old item and fill new values etc...
}

@IBAction func nextButtonPressed(_ sender: UIButton) {
    self.nextItem()
}
Community
  • 1
  • 1

3 Answers3

1

Could also do it with a subclass:

class HelloWorld: UIProgressView {

    func startProgressing(duration: TimeInterval, resetProgress: Bool, completion: @escaping (Void) -> Void) {
        stopProgressing()

        // Reset to 0
        progress = 0.0
        layoutIfNeeded()

        // Set the 'destination' progress
        progress = 1.0

        // Animate the progress
        UIView.animate(withDuration: duration, animations: {
            self.layoutIfNeeded()

        }) { finished in
            // Remove this guard-block, if you want the completion to be called all the time - even when the progression was interrupted
            guard finished else { return }

            if resetProgress { self.progress = 0.0 }

            completion()
        }
    }

    func stopProgressing() {
        // Because the 'track' layer has animations on it, we'll try to remove them
        layer.sublayers?.forEach { $0.removeAllAnimations() }
    }
}

This can be used by calling -startProgressing() when ever you need to start the progressView again from the start. The completion is called when it has finished the animation, so you can change the views etc.

An example of use:

progressView.startProgressing(duration: 5.0, resetProgress: true) {
    // Set the next things you need... change to a next item etc.
}
Mikk Rätsep
  • 512
  • 7
  • 20
  • Thank you. I changed my code as you suggested. This improved my structure, but now progress bar loads once and never resets itself. :( – Mert Diricanlı Nov 14 '16 at 17:02
  • @MertDiricanlı i've updated the code so you can do it. It's also available in https://github.com/doofyus/animatingProgressView – Mikk Rätsep Nov 15 '16 at 10:09
  • I got the same result with my first code. This should be easy task for any programmer. I don't know, maybe I'm doing something wrong. It's been almost 3 hours but I couldn't find a new solution. It works great while I didn't tap the next button. But when I tap it, the progress isn't starting. It starts again after 3 seconds. Did you test your code with this scenario? Huge thanks for your help! – Mert Diricanlı Nov 15 '16 at 13:35
0

You probably need something like this to update your progress bar: self.progressView.setProgress(0, animated: false)

func updateProgressBar() {
    DispatchQueue.main.async {
        self.progressView.setProgress(0.1, animated: false)
    }
    if self.progressView.Progress == 1.0 {
       self.timer.invalidate()
    } else {
       self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(myVievController.updateProgressBar), userInfo: nil, repeats: false)
    }
}

Then you can invalidate the timer and stop the update when you are at 100%

Ramon Vasconcelos
  • 1,466
  • 1
  • 21
  • 28
0

Example

enter image description here

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var progressView: UIProgressView!

    var timer : Timer?
    var timerCount = 0
    var imageArray : Array<UIImage> = []

    // MARK: - Lifecycle -

    override func viewDidLoad() {

        super.viewDidLoad()

        // create gradient images

        buildImageArray(duration: 3, highlightPercentage: 10.0, mainColor: .green, highlightColor: .yellow, imageWidth: Double(progressView.frame.width))

        // set progressView to first image

        progressView.progressImage = imageArray[0]

        // set progressView progress

        progressView.progress = 0.7

        // schedule timer

        if self.timer == nil {

            self.timer = Timer.scheduledTimer(timeInterval: 1/60, target: self, selector: #selector(updateGradient), userInfo: nil, repeats: true)
            RunLoop.main.add(self.timer!, forMode: .common)

        }

    }

    func buildImageArray(duration: Int, highlightPercentage: Double, mainColor: UIColor, highlightColor: UIColor, imageWidth: Double){

        let keyFrames = duration * 60

        for i in 0..<keyFrames {

            // Drawing code

            let frame = CGRect(x: 0.0, y: 0.0, width: imageWidth, height: 1.0)
            let layer = CAGradientLayer()
            layer.frame = frame
            layer.startPoint = CGPoint(x: 0.0, y: 0.5)
            layer.endPoint = CGPoint(x: 1.0, y: 0.5)

            var colors : [UIColor] = []

            for n in 0..<keyFrames {

                colors.append(mainColor)

            }

            let highlightKeyFrames : Int = Int(floor(Double(keyFrames) * (highlightPercentage/100)))

            // 300 * .3 = 90 kf

            for x in 0..<highlightKeyFrames {

                let p = i+x

                if p < keyFrames {

                    colors[p] = highlightColor

                }

            }

            layer.colors = colors.map { $0.cgColor }

            layer.bounds = frame

            let image = UIImage.imageWithLayer(layer: layer)

            imageArray.append(image)

        }

    }

    // updateGradient

    @objc func updateGradient(){

        // crop image to match progress

        let newWidth = self.progressView.frame.width * CGFloat(progressView.progress)

        let progressImage = imageArray[timerCount].crop(rect: CGRect(x: 0, y: 0, width: newWidth, height: 1))

        progressView.progressImage = progressImage

        // increment timer

        timerCount = timerCount + 1

        if timerCount >= imageArray.count {

            timerCount = 0

        }

    }

}

extension UIImage {

    class func imageWithLayer(layer: CALayer) -> UIImage {
        UIGraphicsBeginImageContextWithOptions(layer.bounds.size, layer.isOpaque, 0.0)
        layer.render(in: UIGraphicsGetCurrentContext()!)
        let img = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return img!
    }

    func crop( rect: CGRect) -> UIImage {
        var rect = rect
        rect.origin.x*=self.scale
        rect.origin.y*=self.scale
        rect.size.width*=self.scale
        rect.size.height*=self.scale

        let imageRef = self.cgImage!.cropping(to: rect)
        let image = UIImage(cgImage: imageRef!, scale: self.scale, orientation: self.imageOrientation)
        return image
    }

}
Michael N
  • 436
  • 5
  • 6