Advertisement
iOS SDK

Working with UIRefreshControl

by

When Loren Brichter introduced the idea of "pull to refresh" in Tweetie 2 a few years ago, it wasn't long before developers started to adopt this ingenious and intuitive concept. Even though Twitter now owns the patent on the "pull to refresh" concept, this hasn't stopped Apple from introducing the UIRefreshControl class in iOS 6. This new UIControl subclass makes it trivial to add a "pull to refresh" control to any table view controller in iOS 6.


Short and Sweet

The class reference of UIRefreshControl is short, hinting at how easy it is to get started with this addition of the UIKit framework. The UIRefreshControl class directly descends from UIControl, which means that setting up an instance of UIRefreshControl is not much different from creating and configuring any other UIKit control. After instantiating an instance of the UIRefreshControl class, you assign it to the new refreshControl property of a table view controller object (UITableViewController or a subclass of it). The table view controller takes care of properly positioning and displaying the refresh control. As with any other UIControl subclass, you attach a target-action pair to a specific event, UIControlEventValueChanged in the case of UIRefreshControl.

This wouldn't be a Mobiletuts+ tutorial without an example illustrating how to use the UIRefreshControl class in a project. In the rest of this tutorial, I will show you how to populate a table view with a list of tweets pulled from the Twitter Search API. The request is sent to Twitter's Search API when the user pulls the table view dow: pull-to-refresh.


Step 1: Setting Up the Project

The application that we are about to build queries the Twitter Search API for tweets about iOS development. The request is sent to Twitter's Search API when the user pulls the table view down, revealing the refresh control. We will use the fantastic AFNetworking library to send our request to the Twitter Search API. AFNetworking will also help us to asynchronously download and display profile images.

Create a new project in Xcode by selecting the Empty Application template from the list of templates (Figure 1). Name your application Pull-to-Refresh, enter a company identifier, set iPhone for the device family, and check Use Automatic Reference Counting. The rest of the checkboxes can be left unchecked for this project (figure 2). Tell Xcode where you want to save the project and hit the Create button.

New in iOS 6: UIRefreshControl: Choosing a Project Template - Figure 1
New in iOS 6: UIRefreshControl: Configuring the New Project - Figure 2

Step 2: Adding the AFNetworking Library

Installing AFNetworking using Cocoapods is a breeze. However, in this tutorial, I will show you how to manually add the AFNetworking library to an Xcode project to make sure that we are all on the same page. It isn't that difficult anyway.

Download the latest stable release of the library from its GitHub project page, extract the archive, and drag the folder named AFNetworking to your Xcode project. Make sure that the checkbox labeled Copy items into destination group's folder (if needed) is checked and double-check that the library is added to the Pull-to-Refresh target (figure 3).

New in iOS 6: UIRefreshControl: Add the AFNetworking Library - Figure 3

The AFNetworking library relies on two frameworks that a new Xcode project isn't by default linked against, (1) the System Configuration and (2) Mobile Core Services frameworks. Select your project in the Project Navigator, choose the Pull to Refresh target from the list of targets, and open the Build Phases tab at the top. Expand the Link Binary With Libraries drawer and add both frameworks by clicking the plus button (figure 4).

New in iOS 6: UIRefreshControl: Adding the Required Frameworks - Figure 4

To finish things off, add an import statement for both frameworks as well as AFNetworking to the projects precompiled header file as shown in the snippet below. This will make it easier to work with AFNetworking as we don't need to add an import statement to every class that we want to use the library.

//
// Prefix header for all source files of the 'Pull to Refresh' target in the 'Pull to Refresh' project
//

#import <Availability.h>

#ifndef __IPHONE_3_0
#warning "This project uses features only available in iOS SDK 3.0 and later."
#endif

#ifdef __OBJC__
    #import <UIKit/UIKit.h>
    #import <Foundation/Foundation.h>
    #import <MobileCoreServices/MobileCoreServices.h>
    #import <SystemConfiguration/SystemConfiguration.h>

    #import "AFNetworking.h"
#endif

Step 3: Creating the Table View Controller

The UIRefreshControl is designed to work in conjunction with a table view controller object. Create a new UITableViewController subclass (File > New > File...) by choosing the Objective-C class template from the list of templates (figure 5). Give the new class a name of MTTweetsViewController and double-check that it is a UITableViewController subclass. Tell Xcode that it shouldn't create a nib file for the new controller class by unchecking the checkbox labeled With XIB for user interface (figure 6). Specify a location to save the new class and click the Create button.

New in iOS 6: UIRefreshControl: Creating the TweetsViewController Class - Figure 5
New in iOS 6: UIRefreshControl: Configuring the TweetsViewController Class - Figure 6

Step 4: Adding the Refresh Control

Before we can add the refresh control to the table view controller, we need to instantiate an instance of the new MTTweetsViewController class. Update the application:didFinishLaunchingWithOptions: method in MTAppDelegate.m as shown below. The implementation shouldn't hold any surprises. We initialize an instance of the MTTweetsViewController class and set it as the application window's root view controller. Don't forget to add an import statement at the top of MTAppDelegate.m to import the header file of the MTTweetsViewController class.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Initialize Tweet View Controller
    MTTweetsViewController *vc = [[MTTweetsViewController alloc] initWithStyle:UITableViewStylePlain];

    // Initialize Window
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

    // Configure Window
    [self.window setBackgroundColor:[UIColor whiteColor]];
    [self.window setRootViewController:vc];

    // Make Key and Visible
    [self.window makeKeyAndVisible];

    return YES;
}
#import "MTTweetsViewController.h"

If you run the application in the iPhone Simulator, you should see an empty table view. The refresh control is added in the viewDidLoad method of the tweets view controller. As I mentioned earlier, adding a refresh control is very easy. Take a look at the implementation of the viewDidLoad method shown below. We initialize the refresh control and add a target and action for the UIControlEventValueChanged event of the refresh control. Finally, the refresh control is assigned to the refreshControl property of the table view controller. Of course, the refreshControl property is also new for iOS 6.

- (void)viewDidLoad {
    [super viewDidLoad];

    // Initialize Refresh Control
    UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];

    // Configure Refresh Control
    [refreshControl addTarget:self action:@selector(refresh:) forControlEvents:UIControlEventValueChanged];

    // Configure View Controller
    [self setRefreshControl:refreshControl];
}

Before we build and run the project once more, we need to implement the refresh: action in the view controller's implementation file. To verify that everything is set up properly, we simply log a message to the console. Build and run the project to see the refresh control in action.

- (void)refresh:(id)sender 
{
    NSLog(@"Refreshing");
}

You will notice that the refresh control doesn't disappear after it has been shown by the table view controller. This is something that you will have to do yourself. The idea behind the refresh control is in some ways similar to UIKit's activity indicator view (UIActivityIndicatorView), that is, you are responsible for showing and hiding it. Hiding the refresh control is as simple as sending it a message of endRefreshing. Update the refresh: action as shown below and run the application once more in the iPhone Simulator.

- (void)refresh:(id)sender {
    NSLog(@"Refreshing");

    // End Refreshing
    [(UIRefreshControl *)sender endRefreshing];
}

The refresh control immediately disappears after you release the table view. Of course, this makes the refresh control quite useless. What we will do is send a request to the Twitter Search API and hide the refresh control when we have received a response (or when the request times out). AFNetworking makes this very easy to do.


Step 5: Querying the Twitter Search API

We will store the tweets that we get back from the Twitter Search API in an array. Add a private property named tweets to the MTTweetsViewController class as shown below.

#import "MTTweetsViewController.h"

@interface MTTweetsViewController ()

@property (strong, nonatomic) NSArray *tweets;

@end

Next, update the numberOfSectionsInTableView:, tableView:numberOfRowsInSection:, and tableView:cellForRowAtIndexPath: methods as shown below. If you have worked with table views before, this shouldn't be too difficult to grasp.

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return self.tweets ? 1 : 0;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [self.tweets count] ? [self.tweets count] : 0;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"Cell Identifier";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
    }

    // Fetch Tweet
    NSDictionary *tweet = [self.tweets objectAtIndex:[indexPath row]];

    // Configure Cell
    [cell.textLabel setText:[tweet objectForKey:@"text"]];
    [cell.detailTextLabel setText:[tweet objectForKey:@"from_user"]];

    // Download Profile Image Asynchronously
    NSURL *url = [NSURL URLWithString:[tweet objectForKey:@"profile_image_url"]];
    [cell.imageView setImageWithURL:url placeholderImage:[UIImage imageNamed:@"placeholder"]];

    return cell;
}

In tableView:cellForRowAtIndexPath:, we create a new cell (or dequeue a reusable cell) and populate it with the contents of a tweet. To make sure that the table view scrolls smoothly, we download the user's profile image asynchronously. This is very easy to accomplish with AFNetworking as it gives us setImageWithURL:placeholderImage:. What this does, is setting the cell's image view with the provided placeholder image while requesting the user's profile image in the background. To make this work, add placeholder.png and placeholder@2x.png to your Xcode project. You can find both files in the source files of this tutorial.

We send our request to the Twitter Search API in the refresh: action. Take a look at the updated implementation below. I won't go into the details of how the AFJSONRequestOperation class works in this tutorial, but I do want to explain how the flow of the request works. After specifying the request URL (NSURL) and initializing the URL request (NSURLRequest), we create a JSON request operation by passing (1) the URL request, (2) a success block, and (3) a failure block to JSONRequestOperationWithRequest:success:failure:. The succes block is executed if the request was successful and gives us the response of the request as an instance of NSDictionary. We extract the array of tweets that we requested, update the tweets property, reload the table view to show the tweets, and hide the refresh control by sending it a message of endRefreshing.

- (void)refresh:(id)sender {
    // Create URL
    NSURL *url = [NSURL URLWithString:@"http://search.twitter.com/search.json?q=ios%20development&rpp=100&include_entities=true&result_type=mixed/"];

    // Initialize URL Request
    NSURLRequest *urlRequest = [[NSURLRequest alloc] initWithURL:url];

    // JSON Request Operation
    AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:urlRequest success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
        NSArray *results = [(NSDictionary *)JSON objectForKey:@"results"];

        if ([results count]) {
            self.tweets = results;

            // Reload Table View
            [self.tableView reloadData];

            // End Refreshing
            [(UIRefreshControl *)sender endRefreshing];
        }

    } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
        // End Refreshing
        [(UIRefreshControl *)sender endRefreshing];
    }];

    // Start Operation
    [operation start];
}

If the request fails, we only hide the refresh control. Of course, it would be better to inform the user that the request failed, but this will do for our example. We send the request by starting the JSON request operation at the end of the refresh: action.

Build and run the project once more to see the example application in action. If the profile images are not displaying correctly, then double-check that you have added the placeholder images that I mentioned earlier to your project.

New in iOS 6: UIRefreshControl: The Example Application in Action - Figure 7

Conclusion

There are many libraries that try to mimic the original "pull to refresh" functionality, but it is nice to see that Apple has finally embraced this neat concept and included it in the UIKit framework. As you might have noticed, in iOS 6, Apple has already put the UIRefreshControl class to use in some of its own applications, such as the Podcasts application.

Related Posts
  • Code
    iOS SDK
    Blocks and Table View Cells on iOS94dp7 preview image@2x
    A table view cell doesn't know about the table view it belongs to and that's fine. In fact, that's how it should be. However, people who are new to this concept are often confused by it. For example, if the user taps a button in a table view cell, how do you obtain the index path of the cell so you can fetch the corresponding model? In this tutorial, I'll show you how not to do this, how it's usually done, and how to do this with style and elegance.Read More…
  • Code
    iOS SDK
    Working with NSURLSession: AFNetworking 2.032pzi preview image@2x
    In the previous installments of this series, we've taken a close look at the NSURLSession API introduced in iOS 7 and OS X Mavericks. Networking on iOS and OS X has become much simpler and more flexible thanks to the NSURLSession API. Does this mean that you should stop using AFNetworking for your networking needs? And what about AFNetworking 2.0, which was introduced a few months ago? In this final installment, I will tell you about AFNetworking 2.0 and how it compares to the NSURLSession API.Read More…
  • Code
    iOS SDK
    Working with NSURLSession: Part 4E548b preview image@2x
    In the previous tutorial, we started creating a simple podcast client to put what we've learned about NSURLSession into practice. So far, our podcast client can query the iTunes Search API, download a podcast feed, and display a list of episodes. In this tutorial, we zoom in on another interesting aspect of NSURLSession, out-of-process downloads. Let me show you how this works.Read More…
  • Code
    iOS SDK
    Working with NSURLSession: Part 3E548b preview image@2x
    In the previous tutorials, we explored the fundamentals of the NSURLSession API. There is one other feature of the NSURLSession API that we haven't look into yet, that is, out-of-process uploads and downloads. In the next two tutorials, I will show you how to create a very simple podcast client that enables background downloads.Read More…
  • Code
    iOS SDK
    Networking with NSURLSession: Part 1E548b preview image@2x
    From a developer's perspective, one of the more significant changes in iOS 7, and OS X Mavericks for that matter, is the introduction of NSURLSession. Even though NSURLSession may seem daunting at first glance, it's important that you understand what it is, how it relates to NSURLConnection, and what the differences are. In this series, I will take you through the fundamentals of NSURLSession so you can take advantage of this new technology in your own applications.Read More…
  • Code
    iOS SDK
    Accessing Google Services Using the OAuth 2.0 ProtocolGoogle oauth@2x
    This tutorial will dive into implementing Google Services using the OAuth 2.0 protocol. Read on!Read More…