Lessons From iOS: NSURLConnection

Edit:  NSURLConnection is dead. long live  NSURLSession

I am working on an app for my University, LeTourneau University. The app pulls data from a bunch of web services.  Sometimes these services have nice XML/JSON/RSS interfaces. Sometimes you have to result to good old fashion HTML scrapping. The trick will be how to download the content of a HTML document on iOS.

Usually when you work with HTML you are displaying it in a web view of some flavor. But when you are scrapping HTML you want the content of the HTML doc itself. Browsing around the iOS docs will lead you to some potentially useful classes:

  • NSURL
  • NSURLRequest
  • NSURLConnection
  • NSURLResponse

The NSURL class is straightforward enough. It stores a manipulates URLs. The NSURLRequest is representation of request to load a URL. The NSURLConnection is a way to execute a URL request. The NSURLResponse is the results of the executed request. 

The real key is the NSURLConnection and the various NSURLConnection delegates. NSURLConnectionDelegate, NSURLConnectionDownloadDelegate, and NSURLConnectionDataDelegate are complementary delegates designed to configure a specific NSURLConnection. These delegates have been extensively adjusted, modified, and overhauled. Most of the methods in these delegates have become obsolete since iOS 5 (more on this later).  The first step is to fetching data is to to set up the NSURLConnection. NSURLConnections can be performed synchronously or asynchronously. To prevent blocking the main thread I recommend sending requests asynchronously.

-(void)performRequest
{

    NSURL * url =[[NSURL alloc] initWithString:@"http://myurl.com"];
    NSURLRequest * request = [[NSURLRequest alloc] initWithURL:url];
    NSURLConnection * connection =[[NSURLConnection alloc]initWithRequest:request 
																 delegate:self];
    
}

Once the request  has been performed all the rest of the processing occurs in the delegate methods. If your URL is secure then you will need to implement the following two methods to ensure access.

- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
    NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
    if ([challenge previousFailureCount] == 0)
    {
        NSURLCredential * cred = [NSURLCredential credentialWithUser: @"username"
                                                            password: @"password"
                                                         persistence: NSURLCredentialPersistenceForSession];
        [[challenge sender] useCredential:cred forAuthenticationChallenge:challenge];
    }
    else
    {
        //You don't have authentication
    }
}
-(BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection
{
    return YES;
}

The first method is how to provide authentication for the URL. The second method is to use authentication credentials already stored either in the keychain or earlier in your applications code. Once you started the connection and provided any authentication required you should check for connection failures.

-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    //handle errors
}

The final step is to receive the data and process it.

-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    NSString * html = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(@"%@",html);
}

The didReceiveData method is part of an informal protocol called NSURLConnectionDataDelegate. All of the methods in the NSURLConnection delegate related to receiving data are deprecated. You have three options that aren't deprecated.The NSURLConnectionDataDelegate method shown above. The NSURConnection class provides

+ (void)sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler;

But this method does not have a delegate option for handling authentication. This option only works for unsecured URL's. The last option is to use the NSURLConnectionDownload delegate. This downloads the URL's contents to a temporary file that you can then copy. I tried to get this to work but had a headache trying to debug the errors. Edit: but in the mean time Apple has added the fabulous new NSURLSession class and a whole host of new API's I highly encourage you to check them out.

Lessons From iOS: UISplitViewController

UISplitViewController is one of the fundamental view controllers for the iPad, but using it for the first time can present a challenge. A good example of a split view controller is the mail application. The sidebar(or popover in portrait) on the left lists emails or mailboxes. the main panel on the right shows the content of an email. There are a number of moving pieces that need to fit together. Typically the UISplitViewController contains two UINavigationViewControllers. The view controllers are referenced are UINavigationViewControllers.  The first navigation view controller is called the master view.  The master view usually has a table view controller as its root view controller and appears as sidebar in landscape and a popover in portrait. The second view controller is referred to as the detail view controller. The detail view is typically visible either as a main panel in landscape or as full screen view in portrait.

So usually there is one UISplitViewController, two UINavigationViewControllers, one UITableViewController, and one custom view controller.
To avoid ambiguity I will refer to them as the split controller, detail nav, master nav, master controller, and detail controller to refer respectively to each of these views.

The general preference of the Cocoa frameworks is to delegate rather than to subclass. Chance are there is nothing special about either of your nav views or the master controller so these can be delegated to another object. The same could be done with the UISplitViewController, but if you keep delegating everything further back then your either going to end up with an application delegate that is monstrous, or you have view controllers that are acting as delegates for views they are unrelated to, or you create an object to act as delegate for a collection of things. Since the split controller is all about holding other views it made sense to to subclass UISplitViewController and make the subclass act as the delegate for the views and controllers it contains.

Using this design the split controller adopts the UINavigationController delegate to support the detail nav (master nav is usually static). It will need to support UITableViewDelegate and UITableViewDataSource to support selecting elements from the master controller. It will also need to support UISplitViewController and UIPopoverController delegates(The first to support orientation changes and the latter to present the master view in portrait orientation). If your detail view has a custom delegate, the split controller most also adopt it.

A very natural use of UISplitViewController is to list a collection of items briefly in the master controller and display the object at length in the detail view (Like the Mail or notes app). Another design pattern is to use it as an alternative graphical representation of a tab bar. Instead of listing the tabs along the bottom, the tabs can be listed along the side inside the master controller. In this case the detail controller is not one controller, but one of a collection of controllers.  

There are a number of moving peaces that all need to work together to produce a functioning split controller. The first step is to tie everything together. Somewhere outside the split controller class it needs to have its splitView delegate and its popover delegate set to itself. After that is handled all the rest of the heavy lifting is done inside the split controller class. During my experimentation I found that in order to prevent strange bugs it was necessary to retain the popover controller and the button that presents the popover view.  

@interface LETUSplitController ()

@property (nonatomic, readwrite) UINavigationController * detailNav;
@property (nonatomic, strong) UIBarButtonItem *splitViewButton;
@property UIPopoverController * splitPopover;

@end

There are four key methods to handle the split view behavior. The first is setting delegates and settings. This is easily done inside the viewDidLoad method.

-(void)viewDidLoad
{
  //set the master nav delegates and title
  UITableViewController * masterView = (id)[self.viewControllers[0] topViewController];
  masterView.title = @"myTitle";
  masterView.tableView.delegate = self;
  masterView.tableView.dataSource = self;

  //Set the detail nav delegate
  self.detailNav = self.viewControllers[1];
  self.detailNav.delegate = self;
  
  //set a couple of settings
  masterView.clearsSelectionOnViewWillAppear = NO;
  masterView.contentSizeForViewInPopover = CGSizeMake(320.0, 600.0);
  
  //do your extra stuff here
}

The next step is to configure the splitViewController delegate methods. These methods are responsible for handling the orientation changes and presenting the popover and the button.  

- (void)splitViewController:(UISplitViewController*)splitController 
willHideViewController:(UIViewController *)viewController 
withBarButtonItem:(UIBarButtonItem *)barButtonItem
forPopoverController:(UIPopoverController *)popoverController
{
  barButtonItem.title = @"myTitle";
  [[[self.viewControllers[1] topViewController] navigationItem] setLeftBarButtonItem:barButtonItem animated:YES];
  self.splitViewButton = barButtonItem;
  self.splitPopover = popoverController;
}

- (void)splitViewController:(UISplitViewController *)splitController
     willShowViewController:(UIViewController *)viewController
  invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem
{
  [[[self.viewControllers[1] topViewController] navigationItem] setLeftBarButtonItem:nil animated:YES];
  self.splitViewButton = nil;
  self.splitPopover = nil;
}

The next step is to respond to the user selecting an item from the the mater view. For whatever reason the button likes to disappear after a selection so I just re-inserted it after each selection.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
  UITableViewCell * cell = [tableView cellForRowAtIndexPath:indexPath];
  
  //use the cell to decide what to do. l\Load a new detail view or update the current one
  
  [self.splitPopover dismissPopoverAnimated:YES];
  [[[self.viewControllers[1] topViewController] navigationItem] setLeftBarButtonItem:self.splitViewButton animated:YES];
}

The last step is to handle all of he data source methods for the master view. This is just standard TableViewController stuff.  I had a deuce of a time getting all the moving pieces to work together. What I present here should be the bear bones essentials required to get it all to work. If you have troubles drop me a comment and maybe I can help you. Also let me know if you have questions or problems.

Lessons From iOS: HTTP authentication

I recently became the maintainer of a universal iOS application. The application was designed and built by a student at LeTourneau University. The App is called iLETU. It is a shortcut to the dozens of student related websites and resources that a LeTourneau Student might need to access. One of the most convenient features of the app is an automatic login to a number of student websites. Each of the five websites accessible from the app via auto login require different login methods. After spending time reading the devDocs, searching Stack Overflow, and trying out a few ideas in XCode I think I finally understand how to do HTTP basic authentication on iOS and OS X.

The secret to HTTP basic authentication using cocoa is knowing NSURL and the related classes. 

  • NSURL
  • NSURLRequest/NSMutableURLRequest
  • NSURLConnection
  • NSURLCredential
  • NSURLCredentialStorage
  • NSURLProtectionSpace
  • UIWebView/WebView/NIWebController etc.

If your project actually wants to display web content to the user the no brainier solution is to use Apple's UIWebView(Or WebView if you are on a Mac). For the iLETU app some basic navigation was also necessary so I used NIWebController, a handy view controller for web views from the nimbus framework that adds a toolbar to the basic WebView.

Webviews load data from a NSURLRequest object. NSURLRequests provide basic information for requesting a URL: timeout, HTTP fields, etc. Websites are requested via HTTP GET requests. Most forms on websites are submitted via a HTTP POST request.

But the real magic comes from NSURLConnection. In the words of the devDocs, "An NSURLConnection object provides support to perform the loading of a URL request." If you want to load some a URL in the background without displaying it you would use NSURLConnection. The real power of the NSURLConnection is in the method

+ (NSURLConnection *)connectionWithRequest:(NSURLRequest *)request delegate:(id < NSURLConnectionDelegate >)delegate

The NSURLConnectionDelegate protocol has methods for responding to successful connections, fatal errors, and authentication challenges. If you are trying to access data Protected by HTTP basic authentication this is how Cocoa does it. At this point an example should bring some clarity.  

//basic HTTP authentication
NSURL *url = [NSURL URLWithString: urlString];
NSMutableURLRequest *request;
request = [NSMutableURLRequest requestWithURL:url
                                  cachePolicy:NSURLRequestReloadIgnoringCacheData
                              timeoutInterval:12];
[self.webView openRequest:request];
(void)[NSURLConnection connectionWithRequest:request delegate:self];

This creates a URL. From the URL a URLRequest is created. The URLRequest is then loaded in the web view. The Request is also used to make a URLConnection. We don't really use the connection, but we need to receive notifications about authentication so we set the delegate. There are only two methods we need from the delegate.

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
{   
    NSURLCredential * cred = [NSURLCredential credentialWithUser:@"username"
                                                        password:@"password"
                                                     persistence:NSURLCredentialPersistenceForSession];
    [[NSURLCredentialStorage sharedCredentialStorage]setCredential:cred forProtectionSpace:[challenge protectionSpace]];
    
}

- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection;
{
    return YES;
}

Whenever there is an authentication challenge a credential is added to the credential storage. You also tell the connection to use the credential storage.