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.
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).
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).
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.
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 [email protected] 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.
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.
Comments