Checking network connectivity when displaying iPhone iAds

There was a comment to my post about adding iAds to an application that got me thinking about an improvement to the way I handle the iAd BannerView. The comment was about moving the app to the background, entering airplane mode and then moving the app back to the foreground. That is just one example of the more general problem of changing network connectivity. The problem comes after you have successfully requested and received an Ad and shown the banner view. If at that point network connectivity is lost the banner view is left on screen. If the user clicks on the Ad banner they get a network connection error which is not exactly a great user experience.

You could reason that losing network connectivity after you have displayed the ad banner is not very likely and just live with the problem - after all the app still works, the user just gets an error message. Note that you need to have network connectivity to see the add in the first place as it is only moved onscreen when the request for an ad completes with success. However with iOS 4 the chances of network connectivity changing whilst your app is running is much more likely. An app can spend days suspended in the background before being brought back to the foreground. In that time the user can be in a completely different location with or without network connectivity. Leaving the ad banner onscreen when there is no network connection wastes some screen space than can be put to better use and as I said earlier hardly represents the best user experience we can provide.

Using Reachability to Check Network Connectivity

To summarise the problem I want to be able to control when an iAd banner is displayed or hidden based on whether the device has network connectivity. Thus if the device has no network connectivity I do not bother to request an Ad since it will return an error anyway. Likewise if I am showing the banner and lose connectivity I remove it.

The network state of an iPhone can be be monitored using the SCNetworkReachability interface of the System Configuration framework. Apple provides the Reachability sample application as an example on how to implement the framework which makes it trivial to add to an existing app. The example app was recently updated for iOS 4.0 so if you already have a copy it may be worth checking you are using the latest version (v2.2).

To get started add the reachability code and SystemConfiguration.Framework to the example app I previously used to demonstrate implementing iAds. Copy the Reachability.h and Reachability.m files from the Apple Reachability sample app and use the Add -> Existing Files and Add->Existing Frameworks options in Xcode to add the files and framework to the project:

Reachability Class Setup

The Reachability class provides a convenient wrapper to the SCNetworkReachability interface. To start using it we just need to add an ivar to the application delegate to hold a reachability object and then start the notifier when the application first launches (existing code not shown):

// Reachability.h
@class Reachability;

@interface TabnavAppDelegate : NSObject 
           <UIApplicationDelegate, UITabBarControllerDelegate> {
  ...
  ...
  Reachability *netReach;
}
...
...
@property (nonatomic, retain) Reachability *netReach;

// Reachability.m
#import "Reachability.h"

@implementation TabnavAppDelegate

@synthesize netReach;

- (BOOL)application:(UIApplication *)application
        didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 

  self.netReach = [[Reachability reachabilityWithHostName:@"apple.com"]
                   retain];
  [netReach startNotifier];
  ...
  ...
}

- (void)dealloc {
  [netReach release];
  ...
  ...
}

The reachabilityWithHostName class method takes the name of a host to check - apple.com in this case - and returns a reachability object that we can use to check the network status. The reachability class can also send notifications when the reachability status changes. The startNotifier method instructs the reachability class to start sending reachability changed notifications.

Listening for Reachability Changed Notifications

With the reachability class initialised we can use it in our view controller to check the reachability status and control when to show and hide the iAd BannerView. First I will add a helper method to query the Reachability object to determine if we currently have network connectivity:

- (BOOL)networkReachable {
  
  TabnavAppDelegate *delegate = (TabnavAppDelegate *)[[UIApplication
                                                       sharedApplication]
                                                       delegate];
  Reachability *netReach = [delegate netReach];

  NetworkStatus netStatus = [netReach currentReachabilityStatus];
  BOOL connectionRequired = [netReach connectionRequired];
  
  if ((netStatus == ReachableViaWiFi) ||
      ((netStatus == ReachableViaWWAN) && !connectionRequired)) {
    return YES;
  }
  return NO;
}

This method first retrieves the reachability object we setup in the application delegate and then uses the currentReachabilityStatus and connectionRequired methods to determine network connectivity. There are several possible situations since the device may be connected via WiFi or via the cellular network. Also the cellular network may be available but not actually connected.

We can now use this method to determine the network status when our view controller first loads. If we already have network connectivity we can go ahead and create the iAd BannerView immediately. In addition we will initialise an observer to listen for reachability changed notifications generated by the Reachability class. Each time the network connectivity of the device changes we will get a call to the method reachabilityChanged that we specified when creating the observer:

- (void)viewDidLoad { 
  // If the network is reachable create the iAd banner, otherwise we
  // wait until the network becomes reachable
  BOOL reachable = [self networkReachable];
  
  if (reachable) {
    [self createBannerView];
  }

  // Listen for reachability changes
  [[NSNotificationCenter defaultCenter] addObserver:self
                              selector:@selector(reachabilityChanged:) 
                              name:kReachabilityChangedNotification
                              object:nil];
}

Note that whenever you add an observer for a notification you need to ensure to remove the observer when your view controller is deallocated:

- (void)dealloc {
  [[NSNotificationCenter defaultCenter] removeObserver:self];
  ...
  ...
}

To finish up we just need to implement the reachabilityChanged method that is called whenever a reachability notification is received:

- (void)reachabilityChanged:(NSNotification *)note { 
  BOOL reachable = [self networkReachable];
  
  if (reachable && (self.bannerView == nil)) {
    [self createBannerView];
  }

  if (!reachable && self.bannerView) {
    [self hideBanner];
    [self releaseBanner];
  }
}

This method determines the current reachability status using the helper method we created earlier. Then if the network has just become reachable and we do not currently have a bannerView it is created. Alternatively if network connectivity has just been lost and we currently have a bannerView it is hidden and released. This means that during the life of the application we will add and remove the bannerView as the network connectivity changes.

There are several different strategies we could have used here that would achieve the same result from the users perspective. For example, we could always create the bannerView in viewDidLoad and then call hideBanner and showBanner in reachabilityChanged instead of creating and releasing each time.

To test that everything is working you can try it on a network connected device. Once the test iAd banner appears you can disconnect the network connection using the settings application (or use airplane mode) and you should see the Ad disappear. Likewise when you restore connectivity you should eventually see a new ad banner.

Handling Background App Switching

One other enhancement I considered adding was to release the BannerView when the app is switched to the background and recreate it when moving back to the foreground. This is easy enough to implement by having our view controller listen for UIApplicationDidEnterBackgroundNotification and UIApplicationDidBecomeActiveNotification. However when I checked the background memory usage after releasing the bannerView I found it made very little difference so I have left this out of the sample app.