I try to add progress bar on my application. I found the question on How to add Progress bar to UIAlertController? but it didn't show how to update the progress bar. I simplify the code as below but it didn't update the progress bar (it shows only once the progress is completed). What did i over look? Thank you for your help.
override func viewDidLoad() {
super.viewDidLoad()
var topViewController = UIApplication.shared.delegate!.window!!.rootViewController!
while (topViewController.presentedViewController != nil){
topViewController = topViewController.presentedViewController!
}
DispatchQueue.main.async(execute: {
let alert = UIAlertController(title: "downloading", message: "pls wait", preferredStyle: .alert)
let progressBar = UIProgressView(progressViewStyle: .default)
progressBar.setProgress(0.0, animated: true)
progressBar.frame = CGRect(x: 10, y: 70, width: 250, height: 0)
alert.view.addSubview(progressBar)
topViewController.present(alert, animated: true, completion: nil)
var progress: Float = 0.0
repeat {
DispatchQueue.global(qos: .background).async(execute: {
progress += 0.01
print (progress)
DispatchQueue.main.async(flags: .barrier, execute: {
progressBar.setProgress(progress, animated: true)
})
})
} while progress < 1.0
})
}
One could get just a little confused by all those dispatch queues in your code :-)
I try to explain the problem:
The first (outmost) DispatchQueue.main.async
is excecuted in the main (UI) thread.
It creates the UIAlertController
, stuffs the UIProgressView
into it and displays the dialog. Then it performs in the main thread some time-critial job (repeat-while-loop). Never do so, because it blocks the main thread.
Since the main thread is blocked, no updates are reflected in the UI controls, so you don't see progress changes.
Therefore,
- I put the time critical work item (repeat loop) into the global queue (not the main queue) as an asynchronous work item
- Within that work item, I'll dispatch the progress bar updates into the main queue (to be executed in the main thread)
- At the end, when everything is done, I dismiss the
UIAlertController
You only need the first Dispatch when your alert view is shown from a method like viewDidLoad
or viewWillAppear
, e.g. from a point in time when the view isn't displayed yet. If you call it from a button callback, or viewDidAppear
(e.g. view is visible), you could just skip that outer Dispatch.
Here you go - I also put in some sleep time and modified the GCD-Calls a litte to use trailing closures:
override func viewDidLoad() {
super.viewDidLoad()
var topViewController = UIApplication.shared.delegate!.window!!.rootViewController!
while (topViewController.presentedViewController != nil){
topViewController = topViewController.presentedViewController!
}
// Usage of GCD here is only necessary because it's called from
// viewDidLoad. If called by viewDidAppear or some action callback, you
// don't need it:
DispatchQueue.main.async {
let alert = UIAlertController(title: "downloading", message: "pls wait", preferredStyle: .alert)
let progressBar = UIProgressView(progressViewStyle: .default)
progressBar.setProgress(0.0, animated: true)
progressBar.frame = CGRect(x: 10, y: 70, width: 250, height: 0)
alert.view.addSubview(progressBar)
topViewController.present(alert, animated: true, completion: nil)
var progress: Float = 0.0
// Do the time critical stuff asynchronously
DispatchQueue.global(qos: .background).async {
repeat {
progress += 0.1
Thread.sleep(forTimeInterval: 0.25)
print (progress)
DispatchQueue.main.async(flags: .barrier) {
progressBar.setProgress(progress, animated: true)
}
} while progress < 1.0
DispatchQueue.main.async {
alert.dismiss(animated: true, completion: nil);
}
}
}
}
After help from Andreas comment. I work out with this code.
override func viewDidLoad() {
super.viewDidLoad()
var topViewController = UIApplication.shared.delegate!.window!!.rootViewController!
while (topViewController.presentedViewController != nil){
topViewController = topViewController.presentedViewController!
}
DispatchQueue.main.async(execute: {
let alert = UIAlertController(title: "loading", message: "wait please", preferredStyle: .alert)
let rect = CGRect(x: 10, y: 70, width: 250, height: 0)
let progressView = UIProgressView(frame: rect)
progressView.progress = 0.0
progressView.tintColor = UIColor.blue
alert.view.addSubview(progressView)
topViewController.present(alert, animated: true, completion: nil)
var progress = 0.0
DispatchQueue.global(qos: .background).async(execute: {
repeat {
progress += 0.0001
print (progress)
DispatchQueue.main.async(flags: .barrier, execute: {
progressView.progress = Float(progress)
})
} while progress < 1.0
DispatchQueue.main.async(execute: {
alert.dismiss(animated: true, completion: nil);
})
})
})
}