Activity Indicator not appearing
Asked Answered
F

4

8

I have some heavy code that runs for around 0.2 seconds.

I set up the activity indicator like this; however, it doesn't show up, but rather the whole screen freezes for around 0.2seconds until the code finishes.

func heavyWork() {
    self.actvityIndicator.startAnimating()

    ...
    // heavy loop codes here
    ...

    self.activityIndicator.stopAnimating()
}

Is this the correct way of using the activity indicator?

When I comment out

// self.activityIndicator.stopAnimating()

the activity indicator shows up and stays there - the codes are set up right.

But UI doesn't seem to be updated at the right time.

As i said, the screen just freezes without showing the activity indicator until the heavy code is done.

Fontaine answered 7/7, 2015 at 13:19 Comment(2)
Is your heavy loop code running on a background thread? The heavy loop code will block the UI from displaying the activity indicator. Further if you are calling heavyWork on a background thread the UI shouldn't update as it is not on the main thread.Manatee
if your are on a background thread, you cannot modify anything on UI; if you are on the main thread then it is not the best for doing the heavy work.Prevost
P
15

maybe you want to carry on with such pattern instead:

func heavyWork() {
    self.actvityIndicator.startAnimating()

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), { () -> Void in

        // ...
        // heavy loop codes here
        // ...

        dispatch_async(dispatch_get_main_queue(), { () -> Void in
            self.activityIndicator.stopAnimating()
        })
    });
}

as the heavy work should happen in a background thread and you need to update the UI on a main thread after.


NOTE: obviously it is assumed you call the func heavyWork() on a main thread; if not, you might need to distract the initial UI updates to the main thread as well.

Prevost answered 7/7, 2015 at 13:30 Comment(0)
S
8

If you want the app to be responsive while doing some heavy task, you will need to execute it on a background thread.

This is roughly what's going on here: The main thread of your app executes in a run loop. At the beginning of each loop iteration, the iOS checks for any events (such as user interaction, views changing due to animation, timers being fired, etc.) then queues a bunch of methods to be executed. iOS then goes and executes each of those methods and then, once everything is complete, it updates the display. Then the next run loop iteration begins. Updating the display is costly, so iOS can't do it after every line of code is executed.

So with your code, when you tell the activityIndicator to startAnimating, it tells iOS that at the end of every run loop iteration that the activity indicator image needs to be updated to the next image in the animation sequence. Then, before iOS reaches the end of the current run loop iteration, you are calling stopAnimating which tells iOS that it doesn't need to update the images anymore. So basically you're telling it to stop before it has even started.

You can use Grand Central Dispatch to easily run code on a different thread. It's important to note however that any updates to the UI must be done on the main thread.

func heavyWork() {
    self.activityIndicator.startAnimating()

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {

        // Do heavy work here

        dispatch_async(dispatch_get_main_queue()) {
            // UI updates must be on main thread
            self.activityIndicator.stopAnimating()
        }
    }
}

Also note when programming asynchronously, such as in the above example, you cannot return a value from the asynchronous section in the method that invoked it. E.g. in the example above, you cannot return a result of the heavy work from the heavyWork() method. This is because the function schedules the async code to run on a different thread then returns immediately so it can continue with the current run loop iteration.

SWIFT 4

func heavyWork() {
    activityIndicator.startAnimating()

    DispatchQueue.global(qos: .default).async {

        // Do heavy work here

       DispatchQueue.main.async { [weak self] in
            // UI updates must be on main thread
            self?.activityIndicator.stopAnimating()
        }
    }
}
Swiss answered 7/7, 2015 at 14:4 Comment(1)
DO appreciate for this answer! I have been struggling the whole day about the DispatchQueue and activity indicator.Striated
W
7

SWIFT 5:

func heavyWork() {
    activityIndicator.startAnimating()
    
    DispatchQueue.global(qos: .background).async {
        
        // ...
        // heavy loop codes here
        // ...
        
        DispatchQueue.main.async {
            self.activityIndicator.stopAnimating()
        }
    }
}
Westsouthwest answered 31/1, 2018 at 19:45 Comment(0)
H
1

This happens because you are running the heavy duty code on the main thread. In this way the system never has a chance to commit the graphic transaction until the heavy routine ends. Your start and stop method are committed at the same time.
To avoid that you should run the heavy duty method on another dispatch_queue, but pay attention that most of UIKit objects are not thread safe and should be dispatched again on the main queue.

Helbonia answered 7/7, 2015 at 13:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.