UIRefreshControl Fun and Games

25-Nov-2016 Updated post and sample code for Xcode 8 and iOS 10. The workaround for the bug where the system never called the refresh action method is no longer needed.

The pull-to-refresh mechanism for updating a table view was originally created by Loren Brichter for his Twitter client (later bought by Twitter). It has become such a popular user interface control that Apple added direct support to UIKit in iOS 6.

Refresh control

This post walks you through adding a refresh control to a table view controller using Interface Builder.

UIRefreshControl

The UIRefreshControl object was new in iOS 6.0 and originally only worked with a UITableViewController. Starting with iOS 10 you can also add a refresh control to a UIScrollView which by inheritance also makes it available to scroll, collection and table views.

The table view controller takes care of adding the control in the view hierarchy and adjusting the size and position. When the users pulls the table view down enough to trigger the refresh the control sends the UIControlEventValueChanged event to the action method in the target assigned to the refresh control.

Refresh - An Example Xcode Project

The example Xcode project uses the single view application template. The storyboard has a table view controller that shows a list of cards that we shuffle each time the user refreshes the view. You can find the example project, named Refresh, in my GitHub CodeExamples repository if you want to look at the details but here is a quick description of the setup.

The table view controller has a separate data source:

@interface UYLTableViewController ()
@property (nonatomic,strong) DeckDataSource *deckDataSource;
@end

We create and set the table view data source delegate in the viewDidLoad method:

- (void)viewDidLoad {
  [super viewDidLoad];
  self.deckDataSource = [DeckDataSource new];
  self.tableView.dataSource = self.deckDataSource;
}

The DeckDataSource class adopts the UITableViewDataSource protocol and has a single public method to shuffle the deck:

// DeckDataSource.h
@interface DeckDataSource : NSObject <UITableViewDataSource>
- (void)shuffle;
@end

The private interface defines our model which is an NSMutableArray to hold the cards in the deck:

// DeckDataSource.m
@interface DeckDataSource ()
@property (nonatomic, strong) NSMutableArray *deck;
@end

I will skip the details here but I create the deck with a sequential set of NSNumber integer values in the getter method of the property. The table view data source delegate methods then simply display the NSNumber values, one per row:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
  return self.deck.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView
  cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  static NSString *CellIdentifier = @"BasicCell";
  UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier 
                          forIndexPath:indexPath];
  cell.textLabel.text = [NSString stringWithFormat:@"%@",
                         self.deck[indexPath.row]];
  return cell;
}

The shuffle method does as the name suggests and randomizes the order of the deck. On initial launch the deck is in order. We will use the refresh control to allow the user to shuffle the deck:

Unshuffled deck

Enabling the Refresh Control

To get started we need to add the refresh control to the table view controller. Using Interface Builder select the table view controller in the storyboard and in the attributes inspector change the “Refreshing” option to “Enabled”:

Refresh Control in Interface Builder

I also added a centered title to show “Pull to refresh”. If you do add a title remember to localize it. Once you have added the control it should show up in the documents browser pane of interface builder underneath the table view controller:

You do not need to worry about setting the frame or adding auto layout contraints for the refresh control. The view controller takes care of the visual appearance for you. The only step left for us is to create the method that the system will call when the user pulls down the table to cause a refresh. This sends a valueChanged event that we need to create an action method for in our view controller target.

Adding the Refresh Action

To wire up the event from the UIRefreshControl to a method in our view controller switch to the assistant view in Interface Builder and select the view controller to show it alongside the storyboard. You can then control drag from the refresh control into the view controller code to create the target-action method. Set the name of the method and change the type of the sender to UIRefreshControl

The refresh method in the table view controller shuffles the deck, reloads the table and finally tells the refresh control to stop refreshing:

- (IBAction)refresh:(UIRefreshControl *)sender {
  [self.deckDataSource shuffle];
  [self.tableView reloadData];
  [sender endRefreshing];
}

Don’t forget to call endRefreshing on the refresh control at some point to dismis the control.

An Old Bug

I will mention in passing that there was a bug when Interface Builder first added support for refresh controls. Due to this bug (rdar://14178445) the action in the view controller was never called. Apple fixed this some time ago but if you come across it in old code you can manually set the action called by the control in code as follows:

- (void)viewDidLoad {
  [super viewDidLoad];
  [self.refreshControl addTarget:self
                       action:@selector(refresh:)
                       forControlEvents:UIControlEventValueChanged];
}

Wrapping Up

The UIRefreshControl is a nice addition to UIKit if you need a pull-to-refresh control for your table view. You can find the example Refresh Xcode project in my GitHub CodeExamples repository.