Ruby on Rails, iOS, Git...

We spend a lot of time thinking about these things. If we have something helpful to share, we'll put it here.

Request a free RaddOnline® proposal.

iPhone SDK: Testing Network Reachability

Posted by Tim Stephenson, RaddOnline® on Friday, April 24, 2009

Check Network Resources Before Use

reachability-alert Using Facebook-connect for iPhone SDK to post stories to Facebook is a great feature to add to your iPhone application. But what happens if the user has no access to the network? If you don’t check the network, the answer is nothing. This leads to user confusion, and it will prevent your app from being approved for the App Store.

So, how do you check? Apple provided a sample application called Reachability which provides the answer. I’ll demonstrate here. In several blog posts people felt that the Reachability sample was overkill for their needs. If you agree, here’s a link to a recipe from The iPhone Developer’s Cookbook that provides an alternate solution. Even if you don’t plan on using it, I recommend reading through the code in the Reachability example.

Project Setup

In an earlier post I used the Facebook-Connect for iPhone SDK to post stories to Facebook. I’ll use the same project to demonstrate how to check that Facebook is reachable. Here’s the project with the API Keys and Template Bundle IDs removed. fbconnect-iphone.zip I’ll include a link to the final project at the end.

Download the Reachability project too. I’ll be importing a class from it to check the network connection status.

Adding the SystemConfiguration Framework

sysconfig

The reachability class uses the SystemConiguration Framework to check network reachability. Adding the framework to the project is easy.

  1. Open Connect.xcodeproj if you haven’t already.
  2. In the Groups & Files area, control click on the Frameworks folder and select Add => Existing Frameworks.
  3. Look in the Frameworks folder for the SystemConfiguration.framework folder and select it.
  4. Click Add to add the framework to your project.

Add the Reachability Class

Now add the Reachability Class from Apple’s sample.

  1. Control click on the Source folder in the project and select Add => Existing Files.
  2. Navigate to the Reachability project, click Classes, and select the files: Reachability.h and Reachability.m.
  3. Click Add, then select Copy items into destination group’s folder (if needed)
  4. Click Add again.

Using the Reachability Class

Now that the framework and class have been added to the project, put them to work in the code. Create a variable to track the internet connection status, and two methods in the SessionViewController.h file.

  1. Open the SessionViewController.h file and import the Reachability class:
    #import "Reachability.h"
  2. Add a variable to track the status:
    NetworkStatus internetConnectionStatus;
  3. Add a property to hold the network status:
    @property NetworkStatus internetConnectionStatus;
  4. Add these two methods:
    - (void)reachabilityChanged:(NSNotification *)note;
    - (void)updateStatus;

At this point, the SessionViewController interface should look like this:

#import "FBConnect/FBConnect.h" 
#import "PermissionStatus.h" 
#import "Reachability.h" 

@class FBSession;

@interface SessionViewController : UIViewController
    <FBDialogDelegate, FBSessionDelegate, FBRequestDelegate, PermissionStatusDelegate> {
        IBOutlet UILabel* _label;
        IBOutlet UIButton* _permissionButton;
        IBOutlet UIButton* _feedButton;
        IBOutlet FBLoginButton* _loginButton;
        FBSession* _session;
        PermissionStatus *permissionStatusForUser;
        NetworkStatus internetConnectionStatus;
}

@property(nonatomic,readonly) UILabel* label;
@property (nonatomic, retain) PermissionStatus *permissionStatusForUser;
@property NetworkStatus internetConnectionStatus;

- (void)askPermission:(id)target;
- (void)publishFeed:(id)target;
- (void)reachabilityChanged:(NSNotification *)note;
- (void)updateStatus;

@end

Finally, the implementation.

  1. Open SessionViewController.m.
  2. Sysnthesize the internetConnectionStatus variable:
    @synthesize internetConnectionStatus;
  3. Define a string for the host name of the resource:
    #define kHostName @"www.facebook.com"
  4. Make the viewDidLoad method look like this:
    - (void)viewDidLoad {
        //Use the Reachability class to determine if the internet can be reached.
        [[Reachability sharedReachability] setHostName:kHostName];
        //Set Reachability class to notifiy app when the network status changes.
        [[Reachability sharedReachability] setNetworkStatusNotificationsEnabled:YES];
        //Set a method to be called when a notification is sent.
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reachabilityChanged:) name:@"kNetworkReachabilityChangedNotification" object:nil];
            [self updateStatus];
        [_session resume];
        _loginButton.style = FBLoginButtonStyleWide;
    }
    I want to receive notifications if the network status changes, so I used the setNetworkStatusNotificationsEnabled:YES method. However, I found that the reachabilityChanged method wasn’t called when the network status changed. I’ll refactor later to fix the problem.
  5. Implement the new methods:
    - (void)reachabilityChanged:(NSNotification *)note {
        [self updateStatus];
    }
    
    - (void)updateStatus
    {
        // Query the SystemConfiguration framework for the state of the device's network connections.
        self.internetConnectionStatus    = [[Reachability sharedReachability] internetConnectionStatus];
        if (self.internetConnectionStatus == NotReachable) {
            //show an alert to let the user know that they can't connect...
            UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Network Status" message:@"Sorry, our network guro determined that the network is not available. Please try again later." delegate:self cancelButtonTitle:nil otherButtonTitles:@"OK", nil];
            [alert show];
        }  else {
            // If the network is reachable, make sure the login button is enabled.
            _loginButton.enabled = YES;
        }
    }
    To update the user of the status, I display an alert with a message. When status notifications are sent later, either enable the login button or display the alert.
  6. Add the alert delegate method:
    #pragma mark AlertView delegate methods
    - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
        _loginButton.enabled = NO;
        [alertView release];
    }
    If the network isn’t available, disable the loginButton. In this app nothing can be done without a connection, so additional information would be in order.

To test the application on the simulator, go to Network Preferences and disable your network connections. While disabled, the alert will be displayed. To test on an iPhone, put the device in Airplane mode. On an iPod, turn off the WiFi connection. I also tested on my device by walking to places where there is no Edge or WiFi network. In my tests, the reachabilityChanged method did not get called. Thanks to Shashi Prabhakar for giving me a clue about how to get it working, see his comment below.

Refactoring to Receive Reachability Changed Notifications

To get the change notifications I had to make several small changes. My initial observations when checking the remoteHostStatus instead of the internetConnectionStatus:

  1. The reachabilityChanged method was being called.
  2. I noticed that when first initialized the remoteHostStatus is always NotReachable.
  3. The internetConnectionStatus returns a positive result before the remoteHostStatus.
  4. Because I am using an alert, sometimes the alert would be displayed twice or not at all when using one status or the other.

As a result, I changed the code to track both the remoteHostStatus and the internetConnectionStatus. I also added a method to initialize the variables.

  1. Open SessionViewController.h and add a variable and property to hold the remoteHostStatus.
    NetworkStatus remoteHostStatus;
  2. Also add a method to initialize the variables.
    - (void)initStatus;

Here’s the complete interface.

#import "FBConnect/FBConnect.h" 
#import "PermissionStatus.h" 
#import "Reachability.h" 

@class FBSession;

@interface SessionViewController : UIViewController
    <FBDialogDelegate, FBSessionDelegate, FBRequestDelegate, PermissionStatusDelegate> {
        IBOutlet UILabel* _label;
        IBOutlet UIButton* _permissionButton;
        IBOutlet UIButton* _feedButton;
        IBOutlet FBLoginButton* _loginButton;
        FBSession* _session;
        PermissionStatus *permissionStatusForUser;
        NetworkStatus internetConnectionStatus;
        NetworkStatus remoteHostStatus;
}

@property(nonatomic,readonly) UILabel* label;
@property (nonatomic, retain) PermissionStatus *permissionStatusForUser;
@property NetworkStatus internetConnectionStatus;
@property NetworkStatus remoteHostStatus;

- (void)askPermission:(id)target;
- (void)publishFeed:(id)target;
- (void)reachabilityChanged:(NSNotification *)note;
- (void)updateStatus;
- (void)initStatus;

@end

Edit the implementation.

  1. Open SessionViewController.m and add the initStatus method
    -(void)initStatus {
        self.remoteHostStatus = [[Reachability sharedReachability] remoteHostStatus];
        self.internetConnectionStatus    = [[Reachability sharedReachability] internetConnectionStatus];
    }
  2. Change the viewDidLoad method to call the initStatus method instead of the updateStatus method.
    - (void)viewDidLoad {
        //Use the Reachability class to determine if the internet can be reached.
        [[Reachability sharedReachability] setHostName:kHostName];
        //Set Reachability class to notifiy app when the network status changes.
        [[Reachability sharedReachability] setNetworkStatusNotificationsEnabled:YES];
        //Set a method to be called when a notification is sent.
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reachabilityChanged:) name:@"kNetworkReachabilityChangedNotification" object:nil];
        [self initStatus];
        [_session resume];
        _loginButton.style = FBLoginButtonStyleWide;
    }
  3. Change the updateStatus method to check both variables.
    - (void)updateStatus
    {
        // Query the SystemConfiguration framework for the state of the device's network connections.
        self.remoteHostStatus = [[Reachability sharedReachability] remoteHostStatus];
        self.internetConnectionStatus    = [[Reachability sharedReachability] internetConnectionStatus];
        NSLog(@"remote status = %d, internet status = %d", self.remoteHostStatus, self.internetConnectionStatus);
        if (self.internetConnectionStatus == NotReachable && self.remoteHostStatus == NotReachable) {
            //show an alert to let the user know that they can't connect...
            UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Network Status" message:@"Sorry, our network guro determined that the network is not available. Please try again later." delegate:self cancelButtonTitle:nil otherButtonTitles:@"OK", nil];
            [alert show];
        } else {
            // If the network is reachable, make sure the login button is enabled.
            _loginButton.enabled = YES;
        }
    }

I left the log statement so that you could see how the values change as the network settings are changed. In the simulator, go to network settings and disable your connections while it is running. The reachabilityChanged method is now being called as the network status changes.

Here’s the original sample project without the API Keys and Template Bundle IDs. fbconnect-iphone-reachable.zip

And the refactored sample project. fbconnect-iphone-reachable-2.zip

Comments

Shashi Prabhakar said on Saturday, April 25, 2009:

Thanks for your tutorial. I got the reachabilityChanged method to fire by changing the status method called like so

self.internetConnectionStatus = [[Reachability sharedReachability] remoteHostStatus];

instead of your

self.internetConnectionStatus = [[Reachability sharedReachability] internetConnectionStatus];

Tim Stephenson said on Saturday, April 25, 2009:

Thank you very much. That clue helped greatly. I'll update the sample project and the blog with this change.

Mike said on Monday, April 27, 2009:

Reachability the way you are using it is flawed. The Reachability code was not meant to be used the way you are using it. Reachability will not attempt to turn on the WWAN interface if it isn't already on. For more information dig into the SysConfig header files and scour the Apple dev forums with a search for 'WWAN' and you will find a ton of information form an Apple employee, Eskimo.

That being said, your code is perfectly fine for listening to changes in the network, NOT if the network is available. There are cases where WWAN will be down (for power consumption reasons) and Reachability will report network not available. This is the exact correct response. WWAN isnt up so Reachability should report that it isn't. The big issue here is that Reachability will NOT try to turn WWAN on for you.

A more complete code example would first force the Edge/3G/WiFi to come up by opening a CFStream (or any subclass NSStream, NSURLConnection, etc. I personally open a CFWriteStream. If I write and my writeStreamCallback gets called, we have network). Once you have attempted to kick the WWAN up then you can use Reachability to listen for changes.

I hope that helps someone out there who made the same mistake as me and thought Reachability was all that was required.

Tim Stephenson said on Monday, April 27, 2009:

Hi Mike

Thanks for the advice. I'll look into it and rework the example.

Daan said on Wednesday, July 01, 2009:

Have you already had the chance to incorporate Mike's comments?

Chris said on Monday, July 06, 2009:

Mike can you share share you knowledge with us? Just a small example of CFWriteStream to test net connection. Thank you

Tim Stephenson said on Thursday, July 16, 2009:

Hi

I've been doing some research and not really getting any definitive answers. I'm still working to improve the example. Sorry it is taking so long.

Stephen said on Monday, July 20, 2009:

This is a great tutorial for newbees. Great job!!

Nir Etzion said on Friday, September 04, 2009:

Hi everybody,

I had a few problems, and in order to make the facebook example check for reachability i had to:
1.
change it to use sdk3, so the SystemConfiguration framework will be of sdk3. in the xCode menu, i clicked project ->edit project settings.
2.
the Reachability.h and .m files from the apple example (http://developer.apple.com/iphone/library/samplecode/Reachability/index.html) are different. I took the .h and .m files from the solution (http://www.raddonline.com/assets/95/fbconnect-iphone-reachable-2.zip)

Ricardo Valea said on Tuesday, September 22, 2009:

If it's possible could you update your site for the us of reachability version 2.0. A lot has changed since version 1.5 to 2.0 and your implementations will not work with current Reachability standards.

My only reason for asking is I am not sure that Reachability version 1.5 will be approved by apple anymore

Thanks for you help this example helps out a lot

MikeP said on Wednesday, December 02, 2009:

Did your research come up with anything on kickstarting the WWAN with a CFWriteStream as Mike suggested?

Regards,
MikeP

Daniel Ron said on Sunday, December 27, 2009:

Do any of you know how to detect that the user is connected to 2G network? Our application uses Internet connection and telephone at the same time, but then if the user moves from 3G/WiFi network into 2G, the application is automatically being disconnected without notifying the user why.

marco said on Saturday, January 09, 2010:

Hi, thank so much about tutorial.

I use XCODE 3.2.1 and device 3.1.2
on SIMULATOR works perfectly but on DEVICE I don't view the alert when closed the wi-fi.

How can I do please?

marco said on Saturday, January 09, 2010:

OPS I'm sorry, I was referring only to the control of the connection I did not use facebook.

Rich said on Thursday, January 21, 2010:

Am I right in thinking that Apple's revision of the Reachability code has broken this tutorial's code?

The methods in the Reachability class seem to be different to the ones used in the viewDidLoad method of this tutorial.

There is of course the chance that I'm barking up the wrong tree. Could someone please confirm?

Rich said on Thursday, January 21, 2010:

Scratch that - I should have read all the comments first!

Jasvinder said on Friday, May 14, 2010:

Does it checks for all WiFi, Edge and 3G connection?

I don't want my app to be rejected.

Jasvinder said on Friday, May 14, 2010:

This app doesn't checks the network on Edge..Please update yourself

Wider layout said on Thursday, July 29, 2010:

Please use a wider Web site layout. I shouldn't have to scroll horizontally to read your code.

online education degree said on Thursday, September 23, 2010:

Pretty good post. I just stumbled upon your blog and wanted to say that I have really enjoyed reading your blog posts. Any way I' ll be subscribing to your feed and I hope you post again soon.

Ram said on Friday, October 08, 2010:

Good Tutorials for reachability..

sales tax said on Friday, December 31, 2010:

I’ve recently started a blog, the information you provide on this site has helped me tremendously. Thank you for all of your time & work.

Google sketchup said on Sunday, January 02, 2011:

a very inspiring sroties…great thanx to you and to all the people who develope this site. keep going and good luck.

Denon Receivers said on Monday, January 03, 2011:

This is a good,common sense article.Very helpful to one who is just finding the resouces about this part.It will certainly help educate me.

employer identification number said on Sunday, January 16, 2011:

nice post. thanks.