Basics of Pull to Refresh for Swift Developers

Updated on September 21, 2016 – Swift 3.0

Implementing “pull to refresh” is a common need that arises when working with table views. There are typically two scenarios that folks find themselves in when attempting to implement this feature:

  1. They’re working with a UITableViewController
  2. They’re working with a non-UITableViewController, but their view incorporates a regular UITableView, either taking up the whole screen, or a smaller portion of it

This entry will explore both scenarios to help you get up and running quickly with implementing pull to refresh for your Swift iOS app.

Example scenario

For this guide, suppose that we have a list of movies that we’d like to display in a table view. Pulling to refresh will fetch more movies and update the table view to show the new ones.

If you’re the type that likes to simply dive into a working example, both implementations are available to download from GitHub:

Example projects

Note: Code in the main article below is written in Swift 3.0, but code examples for Swift 2.3 are found in the example projects above.

Movies are represented by a simple struct:

The table view (regardless of whether we use a UITableViewController or a regular UIViewController) has the following setup…

Initial data source values:

Setting up the table view’s data source protocol methods depends on whether you’re using a UITableViewController or a regular UIViewController with a table view as one of its content views, so we’ll cover those in the individual examples.

Implementing with UITableViewController

Finish example setup

When working with a UITableViewController, we simply override the data source method implementations. The following is how I’ve chosen to do it for this example:

Enable refreshing in Storyboard

When you’re working with a UITableViewController, the solution is fairly simple: First, Select the table view controller in your storyboard, open the attributes inspector, and enable refreshing:

Table View Controller - Enable Refreshing

A UITableViewController comes outfitted with a reference to a UIRefreshControl out of the box. You simply need to wire up a few things to initiate and complete the refresh when the user pulls down.

Override viewDidLoad()

In your override of viewDidLoad(), add a target to handle the refresh as follows:

Here are a couple of things to observe about the code above:

  1. Swift’s new #selector feature helps with specifying which action will handle the refresh. Since I’ve specified ViewController.handleRefresh(_:) (note the underscore and the colon!) as the action argument, I need to define a function in this UITableViewController class with the same name. Additionally, the function should take one argument.
  2. We’d like this action to be called for the UIControlEvent called ValueChanged.

Implement handleRefresh function

The handleRefresh: function may look something like the following:

That should complete the pull to refresh implementation when you’re working with a UITableViewController!

Implementing with regular view controller + UITableView

Finish example setup

When working with a regular UIViewController, there are a few extra steps involved in getting things set up:

  1. Create an IBOutlet from the storyboard to the view controller
  2. Wire up the table view’s data source and delegate from the storyboard
  3. Implement the required table view data source methods

Creating the IBOutlet is a matter of control+clicking and dragging from the table view in the Storyboard to the view controller code to create the outlet.

To wire up the table view’s data source and delegate in the Storyboard, control+click the table view and drag up to the yellow view controller icon:

Wire up table view's data source and delegate

The data source protocol method implementations may look something like this:

Set up UIRefreshControl

Whereas a UITableViewController comes pre-fit with a UIRefreshControl, a regular UIViewController does not. It’s simple enough to set one up though. Here is a snippet defining a lazily instantiated variable which creates and configures a UIRefreshControl:

The most complicated thing about the code I just proposed is how the UIRefreshControl instance is assigned lazily by means of the closure expression denoted by = { // ...closure body with setup code... }() in the above snippet. Using this approach allows me to complete the setup all in one spot without the use of optionals. You may prefer doing this another way. The bottom line goal is to have a UIRefreshControl instance that we can add to the table view (coming up).

As for the body of the closure expression, we’re adding a target-action to the UIRefreshControl instance, just like we did when we were dealing with a UITableViewController.

As with the UITableViewController example, note:
1. Since I’ve specified “handleRefresh:” (note the colon!) as the action argument, I need to define a function in this UITableViewController class with the same name. Additionally, the function should take one argument.
2. We’d like this action to be called for the UIControlEvent called ValueChanged.

Override viewDidLoad

Assuming that there is an outlet to the table view in the Storyboard, the next step is to add the UIRefreshControl as a subview to the table view:

Implement handleRefresh function

The handleRefresh function is implemented exactly as it was when we were dealing with a UITableViewController:

Wrapping up

Implementing “pull to refresh” is a common need that arises when working with table views. Here we’ve explored how to implement this feature using both a UITableViewController and with a regular view controller and a table view.

  • Michael Vaughn

    Andrew – you write to wire up the table view option+click then drag. Shouldn’t this be control+click?

    • Andrew Bancroft

      You are absolutely right! I need to fix that wording – thank you for pointing it out!

    • Andrew Bancroft

      Wanted to say “thanks” one more time for pointing out the mistake in this post. For some reason, I made the same goof up in another post, and I caught it because you pointed it out! I think I got them all corrected now.

  • Brian R. James

    Excellent article Andrew. Exactly what I was looking for!

  • Ram

    Andrew, thanks for this super helpful tutorial. Some of the most easy to follow iOS dev articles I’ve seen around.

    I followed your method to add a refresh control to my app but the spinner ended up on the right side of the screen. Thought it might have something to do with my layout (it’s just a single table view) but when I cloned your example, I saw the same behaviour.

    Wonder if something changed in the time since this was published. I am running iOS 8.3 and XCode 6.3.2

    I used the regular view controller + UITableView approach and cloned the corresponding app to test.

    • Andrew Bancroft

      Hey Ram!

      Thanks so much for pointing this out to me – I’ll do some checking and see what kind of weirdness I can resolve.

  • Mike Wirth

    Andrew,

    Thanks for the informative tutorial. Couple issues, though:

    1. in your second case, with a non-UITableViewController, you treat that as a variation on the first case. Not entirely clear then what applies and what doesn’t apply in terms of the coding steps you describe.

    2. Did successfully get to the step of adding the lazy var, refreshControl, but the definition of the closure causes the current Swift compiler to complain. Specifically the line:

    refreshControl.addTarget(self, action: “handleRefresh:”, forControlEvents: UIControlEvents.ValueChanged)

    produces a compiler error. Doesn’t like the arg list, appears to be an issue with “self”. Using Xcode 6.3.2, not the just released 6.4, BTW. Any hints on how to correct this? (I’m using optionals for now.)
    Mike

  • Jimbo

    You are the best Andrew! Super clear.

  • Gibong Kim

    Thanks for your help!! This sharing is very good.

  • Kakubei

    Thanks for this Andrew. Just wanted to point out that sort is not doing anything here. You want sortInPlace. However, that one produces a problem because it will repeat the first entries if the new ones end up above them alphabetically.

    • Andrew Bancroft

      Oh snap! Let me look into this, Kakubei – thanks for the heads-up!

  • kirti kumari

    Hey Andrew!
    Thanks for this tutorial.Your tutorials are always helpful.But here i do have a problem with pull to refresh.I am implementing it with “table view on content view controller” + “pageViewController” + “main view controller”….and it’s refresh action method is not calling. Although action method is calling in case of simple table view + viewController .I do not understand what is the problem here.It would be grateful if you could help me out.Thanks!!

  • Alan Mills

    Hi Andrew

    Another great tutorial, I always wanted to add this feature but thought it might be too complicated for me but you made it seem easy. Thanks.

  • Matthew Benjamin

    Hey Andrew,

    This is an awesome, quick tutorial. Is there anyway to expand to add the date/time of last update on the pull?

    Thanks!

  • Craig Pearce

    Love this! Just one note, a few changes in the code are required. Might be best to update post, however XCode does offer the correct solution

    Old = “for: UIControlEvents.valueChanged)”
    New = “forControlEvents: UIControlEvents.ValueChanged)”

    • Pär Karlsson

      it’s the other way around. forControlEvents is the Obj-C way and thus the old way.

  • Dragoș Mihai Marcean

    Nice tutorial! Thanks :)

  • Lorenzo Leon

    Hi there Andrew! Awesome and clear explanation. I ended up changing the parameter names in “handleRefresh” so that it doesn’t need a name… otherwise the compiler kept telling me that the viewController class doesn’t have that function… (like this “func handleRefresh(_ refreshControl: UIRefreshControl)” ) is this supposed to happen?

    • Lorenzo Leon

      Duh… just checked the git… that’s exactly what you did… just got confused