Updating the UI Using Dispatch_Async in Swift
In my code I have a simple for loop that loops 100 times with nested for loops to create a delay. After the delay, I am updating a progress view element in the UI through a dispatch_async. However, I cannot get the UI to update. Does anyone know why the UI is not updating? Note: The print statement below is used to verify that the for loop is looping correctly.
for i in 0..<100 {
//Used to create a delay
for var x = 0; x<100000; x++ {
for var z = 0; z<1000; z++ {
}
}
println(i)
dispatch_async(dispatch_get_main_queue()) {
// update some UI
self.progressView.setProgress(Float(i), animated: true)
}
}
Three observations, two basic, one a little more advanced:
-
Your loop will not be able to update the UI in that main thread unless the loop itself is running on another thread. So, you can dispatch it to some background queue. In Swift 3:
DispatchQueue.global(qos: .utility).async { for i in 0 ..< kNumberOfIterations { // do something time consuming here DispatchQueue.main.async { // now update UI on main thread self.progressView.setProgress(Float(i) / Float(kNumberOfIterations), animated: true) } } }
In Swift 2:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { for i in 0 ..< kNumberOfIterations { // do something time consuming here dispatch_async(dispatch_get_main_queue()) { // now update UI on main thread self.progressView.setProgress(Float(i) / Float(kNumberOfIterations), animated: true) } } }
Also note that the progress is a number from 0.0 to 1.0, so you presumably want to divide by the maximum number of iterations for the loop.
-
If UI updates come more quickly from the background thread than the UI can handle them, the main thread can get backlogged with update requests (making it look much slower than it really is). To address this, one might consider using dispatch source to decouple the "update UI" task from the actual background updating process.
One can use a
DispatchSourceUserDataAdd
(in Swift 2, it's adispatch_source_t
ofDISPATCH_SOURCE_TYPE_DATA_ADD
), postadd
calls (dispatch_source_merge_data
in Swift 2) from the background thread as frequently as desired, and the UI will process them as quickly as it can, but will coalesce them together when it callsdata
(dispatch_source_get_data
in Swift 2) if the background updates come in more quickly than the UI can otherwise process them. This achieves maximum background performance with optimal UI updates, but more importantly, this ensures the UI won't become a bottleneck.So, first declare some variable to keep track of the progress:
var progressCounter: UInt = 0
And now your loop can create a source, define what to do when the source is updated, and then launch the asynchronous loop which updates the source. In Swift 3 that is:
progressCounter = 0 // create dispatch source that will handle events on main queue let source = DispatchSource.makeUserDataAddSource(queue: .main) // tell it what to do when source events take place source.setEventHandler() { [unowned self] in self.progressCounter += source.data self.progressView.setProgress(Float(self.progressCounter) / Float(kNumberOfIterations), animated: true) } // start the source source.resume() // now start loop in the background DispatchQueue.global(qos: .utility).async { for i in 0 ..< kNumberOfIterations { // do something time consuming here // now update the dispatch source source.add(data: 1) } }
In Swift 2:
progressCounter = 0 // create dispatch source that will handle events on main queue let source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue()); // tell it what to do when source events take place dispatch_source_set_event_handler(source) { [unowned self] in self.progressCounter += dispatch_source_get_data(source) self.progressView.setProgress(Float(self.progressCounter) / Float(kNumberOfIterations), animated: true) } // start the source dispatch_resume(source) // now start loop in the background dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { for i in 0 ..< kNumberOfIterations { // do something time consuming here // now update the dispatch source dispatch_source_merge_data(source, 1); } }