The network activity indicator in iOS is the spinning wheel in the upper left of the screen in the status bar next to the carrier name and connection strength meters. The indicator spins when the device is using the network offering the user great feedback when they are using data. The indicator should be used for asynchronous network activity that will take more than a second or two. You can easily turn this on or off in your app when making network requests by setting a property on UIApplication. See the code below to simply turn the network activity indicator on or off.

// Get the shared UIApplication
(UIApplication *)app = [UIApplication sharedApplication];

// Toggle the network activity indicator
// Check if the indicator is on
if (!app.isNetworkActivityIndicatorVisible) {

    // Turn the indicator on
    app.networkActivityIndicatorVisible = YES;

} else {

    // Turn the indicator off
    app.networkActivityIndicatorVisible = NO;
}

This is a relatively simple interface API and it is very easy to turn the network activity indicator on and off while a network request is being performed. The drawback of this simple property is that if 2 asynchronous network requests are made and each is responsible for turning off the network activity indicator, then the first network request will turn off the indicator and the second request will still be in progress with no indicator. It can quickly become challenging to manage all running network activities without adding some more API to the activity indicator.

Allowing Multiple Threads/Requests to Use the Network Activity Indicator

The solution to the issue of the first request turning off the network activity indicator can be easily solved by tracking the number of running activities and only turning off the indicator after the last request has completed. This can be accomplished in iOS 7 by adding a category. Another alternative is to create a singleton to keep track of activities and turn the indicator on and off. In either case, the general implementation is the same, below is a demo of creating a singleton to manage the network activity monitor.

 

Public Interface

The class I created to manage the indicator is JSNetworkActivityIndicatorManager, here is the public interface.

// JSNetworkActivityIndicatorManager.h

#import <Foundation/Foundation.h>

@interface JSNetworkActivityIndicatorManager : NSObject

// Get class singleton
+ (JSNetworkActivityIndicatorManager *)sharedManager;

// Show network activity indicator
// Each call adds an activity to the internal queue
- (void)startActivity;

// Hide network activity indicator
// Will not hide the indicator until all activities are complete
- (void)endActivity;

// Hide the network activity indicator
// This will hide the indicator regardless of how many activities have been started
- (void)allActivitiesComplete;

@end

 

Create the Singleton

Start by creating the sharedManager method to return the class instance.

//JSNetworkActivityIndicatorManager.m

+ (JSNetworkActivityIndicatorManager *)sharedManager {

    static JSNetworkActivityIndicatorManager *sharedInstance = nil;
    static dispatch_once_t token;
    dispatch_once(&token, ^{
        sharedInstance = [[self alloc] init];
    });

    return sharedInstance;
}

 

Create the Start and End Methods

I use two properties in this class, the shared UIApplication and an integer called tasks to keep track of activities. The method to start an activity will turn on the indicator and increment the tasks property. The method to end an activity will decrement the tasks property and turn off the indicator if no tasks remain.

// JSNetworkActivityIndicatorManager.m

- (void)startActivity {

    @synchronized(self)
    {
        if (self.application.isStatusBarHidden) {
            return;
        }

        if (!self.application.isNetworkActivityIndicatorVisible) {
            self.application.networkActivityIndicatorVisible = YES;
            self.tasks = 0;
        }

        self.tasks++;
    }
}

- (void)endActivity {

    @synchronized(self)
    {
        if (self.application.isStatusBarHidden) {
            return;
        }

        self.tasks--;

        if (self.tasks <= 0) {
            self.application.networkActivityIndicatorVisible = NO;
            self.tasks = 0;
        }
    }
}

Note that I’m using the synchronize directive to make the class thread safe per Apple’s documentation. I also check to make sure the status bar is actually visible before messing with the network activity indicator.

Using the class is no more difficult than the UIApplication API.

Start a task.

// SomeClassThatUsesNetwork.m

// Start a network task
[[JSNetworkActivityIndicatorManager sharedManager] startActivity];

In the network request completion handler end the task.

// SomeClassThatUsesNetwork.m

// End a network task
[[JSNetworkActivityIndicatorManager sharedManager] endActivity];

Clear all tasks from the manager and just turn off the indicator.

// SomeClassThatUsesNetwork.m

// End a network task
[[JSNetworkActivityIndicatorManager sharedManager] allActivitiesComplete];

 

Complete Class Implementation

Here is the full implementation for the JSNetworkActivityIndicatorManager class.

// JSNetworkActivityIndicatorManager.m

#import "JSNetworkActivityIndicatorManager.h"

@interface JSNetworkActivityIndicatorManager()

@property (nonatomic) NSInteger tasks;
@property (strong, nonatomic) UIApplication *application;
@end

@implementation JSNetworkActivityIndicatorManager

+ (JSNetworkActivityIndicatorManager *)sharedManager {

    static JSNetworkActivityIndicatorManager *sharedInstance = nil;
    static dispatch_once_t token;
    dispatch_once(&token, ^{
        sharedInstance = [[self alloc] init];
    });

    return sharedInstance;
}

- (void)startActivity {

    @synchronized(self)
    {
        if (self.application.isStatusBarHidden) {
            return;
        }

        if (!self.application.isNetworkActivityIndicatorVisible) {
            self.application.networkActivityIndicatorVisible = YES;
            self.tasks = 0;
        }

        self.tasks++;
    }
}

- (void)endActivity {

    @synchronized(self)
    {
        if (self.application.isStatusBarHidden) {
            return;
        }

        self.tasks--;

        if (self.tasks <= 0) {
            self.application.networkActivityIndicatorVisible = NO;
            self.tasks = 0;
        }
    }
}

- (void)allActivitiesComplete {

    @synchronized(self)
    {
        if (self.application.isStatusBarHidden) {
            return;
        }

        self.application.networkActivityIndicatorVisible = NO;
        self.tasks = 0;
    }
}

- (UIApplication *)application {

    if (!_application) {
        _application = [UIApplication sharedApplication];
    }

    return _application;
}

- (JSNetworkActivityIndicatorManager *)init {

    self = [super init];

    self.tasks = 0;

    return self;
}

@end