Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Activity Indicator not appearing

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.

like image 675
Joon. P Avatar asked Jul 07 '15 13:07

Joon. P


4 Answers

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.

like image 188
holex Avatar answered Oct 23 '22 12:10

holex


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()
        }
    }
}
like image 23
JoGoFo Avatar answered Oct 23 '22 12:10

JoGoFo


SWIFT 4:

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

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

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

        DispatchQueue.main.async {
            self.actvityIndicator.stopAnimating()
        }
    }
}
like image 6
InnisBrendan Avatar answered Oct 23 '22 11:10

InnisBrendan


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.

like image 1
Andrea Avatar answered Oct 23 '22 12:10

Andrea