11

So I am using an NSTimer to let the user know the app is working. The progress bar is set up to last 3 seconds, but when running, it displays in a 'ticking' motion and it is not smooth like it should be. Is there anyway I can make it more smooth - I'm sure just a calculation error on my part....

If anyone could take a look that would be great. Here is the code:

import UIKit

class LoadingScreen: UIViewController {


    var time : Float = 0.0
    var timer: NSTimer?

    @IBOutlet weak var progressView: UIProgressView!


    override func viewDidLoad() {
        super.viewDidLoad()

// Do stuff

timer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector:Selector("setProgress"), userInfo: nil, repeats: true)

}//close viewDidLoad

  func setProgress() {
        time += 0.1
        progressView.progress = time / 3
        if time >= 3 {
            timer!.invalidate()
        }
    }

}
dwinnbrown
  • 3,789
  • 9
  • 35
  • 60

4 Answers4

27

Edit: A simple 3 second UIView animation (Recommended)

If your bar is just moving smoothly to indicate activity, possibly consider using a UIActivityIndicatorView or a custom UIView animation:

override func viewDidAppear(animated: Bool)
{
    super.viewDidAppear(animated)

    UIView.animateWithDuration(3, animations: { () -> Void in
        self.progressView.setProgress(1.0, animated: true)
    })
}

Make sure your progressView's progress is set to zero to begin with. This will result in a smooth 3 second animation of the progress.

Simple animated progress (Works but still jumps a bit)

https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIProgressView_Class/#//apple_ref/occ/instm/UIProgressView/setProgress:animated:

func setProgress() {
    time += 0.1
    progressView.setProgress(time / 3, animated: true)
    if time >= 3 {
        timer!.invalidate()
    }
}

Option with smaller intervals. (Not recommended)

Set your timer to a smaller interval:

timer = NSTimer.scheduledTimerWithTimeInterval(0.001, target: self, selector:Selector("setProgress"), userInfo: nil, repeats: true)

Then update your function

func setProgress() {
    time += 0.001
    progressView.setProgress(time / 3, animated: true)
    if time >= 3 {
        timer!.invalidate()
    }
}
mbottone
  • 1,219
  • 9
  • 13
  • Thanks that's better but still not as smooth as it should be. Any ideas? Would smaller intervals help or is it likely to be the simulator being slow? – dwinnbrown Jul 15 '15 at 18:40
  • Smaller intervals might help, but is this loading bar always moving at the same speed? More like an activity indicator? Or does it actually respond to loading events and move that way? – mbottone Jul 15 '15 at 18:44
  • How would I set the intervals to be smaller - sorry never used this before and very new to programming :P – dwinnbrown Jul 15 '15 at 18:57
  • Thanks for doing that - I really appreciate it! I am using your first option and the only issue is that the bar is almost invisible at the beginning and slowly gets brighter to the full blue at the end... strange no? – dwinnbrown Jul 15 '15 at 19:39
  • Can you edit your initial question with your current `LoadingScreen` file? Maybe there is something that's causing that in there. – mbottone Jul 15 '15 at 19:43
  • Hmm, nothing looks out of place, so I'm not entire sure. Might be something set in the storyboard where the alpha starts at 0, but that's a wild guess. – mbottone Jul 15 '15 at 19:59
  • Alpha is set to 1. It is quite a strange issue. It is almost like the alpha has been set to 50% at the beginning and then at the end is 100% – dwinnbrown Jul 15 '15 at 20:15
  • Thanks. I used a combination of animate w/ duration but within a set interval (not smaller intervals). Any reason that would be a bad idea? Works smoothly for me.... – Sean Dec 22 '15 at 12:52
  • there is some way to call again this solution? "A simple 3 second UIView animation (Recommended) " I need to start to loading again with the same time – Pedro Manfredi Aug 03 '16 at 13:12
3

For continues loader

timer = Timer.scheduledTimer(timeInterval: 0.001, target: self, selector: #selector(setProgress), userInfo: nil, repeats: true)

and

func setProgress() {
        time += 0.001
        downloadProgressBar.setProgress(time / 3, animated: true)
        if time >= 3 {
            self.time = 0.001
            downloadProgressBar.progress = 0
            let color = self.downloadProgressBar.progressTintColor
            self.downloadProgressBar.progressTintColor = self.downloadProgressBar.trackTintColor
            self.downloadProgressBar.trackTintColor = color
        }
Krishna Kirana
  • 438
  • 4
  • 10
2

It's hard to say exactly what the problem is. I would like to see the output if you put a print line in setProgress to print a timestamp. Is it actually firing every tenth of a second? My guess is that it is not.

Why not? Well, the timer schedules a run loop task in the main thread to execute the code in setProgress. This task cannot run until tasks in front of it in the queue do. So if there are long running tasks happening in your main thread, your timer will fire very imprecisely. My first suggestion is that this is perhaps what is happening.

Here is an example:

  1. You start a timer to do something every second.
  2. Immediately after, you start a long running main thread task (for example, you try to write a ton of data to a file). This task will take five seconds to complete.
  3. Your timer wants to fire after one second, but your file-writing is hogging the main thread for the next four seconds, so the timer can't fire for another four seconds.

If this is the case, then to solve the problem you would either need to move that main thread work to a background thread, or else figure out a way to do it while returning to the run loop periodically. For example, during your long running main thread operation, you can periodically call runUntilDate on your run loop to let other run loop tasks execute.

Note that you couldn't just increment the progress bar fill periodically during the long running main thread task, because the progress bar will not actually animate its fill until you return to the run loop.

Aurast
  • 3,189
  • 15
  • 24
  • Whilst the loading bar is going, the device is using a 'delay' function also using the NSTimer which fires a function after 3 seconds. – dwinnbrown Jul 15 '15 at 18:57
  • I'm not sure what you mean, if you think it's relevant please update the question with some more details. An NSTimer itself doesn't block the main thread, only what happens in its callback does. – Aurast Jul 15 '15 at 19:04
2

What about proper way for animating changes: animateWithDuration:animations: or CABasicAnimation. You can use this for creating smooth animations

Pr0Ger
  • 528
  • 6
  • 15