How to Make an iOS Custom Pull-To-Refresh

Written by: on May 20

"The best way to go about creating a custom pull-to-refresh is to forget UIRefreshControl altogether."

Recently, there has been some debate aroundCustom Pull-To-Refresh the pull-to-refresh control in iOS apps, started by articles like Why The Pull-To-Refresh Gesture Must Die. Even Double Encore’s Nick Arnott chimed in with Why Pull-To-Refresh Isn’t Such A Bad Guy. Regardless of how you feel about the pull-to-refresh, it has become one of the most popular controls in iOS apps, and it is also an excellent place for designers and developers to have some fun. A custom pull-to-refresh can add some personality to your app without compromising the cleanliness of your app’s interface. However, creating a custom pull-to-refresh can be a challenge. UIKit provides UIRefreshControl for quickly dropping a pull-to-refresh into a UITableView or, although not directly documented by Apple, any UIScrollView. UIRefreshControl has a simple interface and is easily integrated into a UIScrollView ; however, it leaves little room for customization. 

Due to UIRefreshControl‘s extensive limitations and lack of flexibility, the best way to go about creating a custom pull-to-refresh is to forget UIRefreshControl altogether, and, instead, start with a UIControl subclass. Subclassing UIControl ,instead of UIView ,gives access to all of UIControl’s action and touch handling methods. This provides an excellent base on which to build our custom pull-to-refresh, and building out the custom pull-to-refresh’s view is the same as building out any other iOS view. However, getting a custom pull-to-refresh to interact correctly with a UIScrollView (or any subclass of UIScrollView like UITableView or UICollectionView) is a more complex problem.

The first step is positioning the custom pull-to-refresh correctly so that its default location is scrolled up off the top of the screen. We also want to make sure that when the UIScrollView is scrolled beyond the top of its content, it will bounce back to this default position (custom pull-to-refresh off the top of the screen) to mimic UIRefreshControl‘s behavior. In order to accomplish this, the custom pull-to-refresh will need to be added as a subview of the UIScrollView (or subclass thereof) and then have its y coordinate set to the negative of its height.

- (void)viewDidLoad
{
    [super viewDidLoad];

    CGFloat customRefreshControlHeight = 50.0f;
    CGFloat customRefreshControlWidth = 320.0f;
    CGRect customRefreshControlFrame = CGRectMake(0.0f,
                                                  -customRefreshControlHeight,
                                                  customRefreshControlWidth,
                                                  customRefreshControlHeight);

    self.customRefreshControl = [[CustomRefreshControl alloc] initWithFrame:customRefreshControlFrame];

    [self.tableView addSubview:self.customRefreshControl];
}

The next step is getting our custom pull-to-refresh to fire an action once the scroll view that contains it has scrolled far enough and then ended dragging. This requires the pull-to-refresh to know how far it has been scrolled and when the scroll view’s end-dragging event has occurred. There are a couple of ways to accomplish this. One way is to set the custom pull-to-refresh as the scroll view’s delegate and implement the UIScrollViewDelegate methods within the custom pull-to-refresh class. However, this means that a more useful object, such as your view controller, can no longer be the scroll view’s delegate. A second, more flexible way is to add a method to your custom pull-to-refresh that informs it that the containing scroll view has been scrolled, and the dragging has ended. This method can be called from wherever the scroll view’s delegate implements the [UIScrollViewDelegate scrollViewDidEndDragging:willDecelerate:] method.

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
    [self.customRefreshControl containingScrollViewDidEndDragging:scrollView];
}

Within the implementation of containingScrollViewDidEndDragging: , you can then check the scroll view’s content offset and fire an action when appropriate.

- (void)containingScrollViewDidEndDragging:(UIScrollView *)containingScrollView
{
    CGFloat minOffsetToTriggerRefresh = 50.0f;
    if (containingScrollView.contentOffset.y <= -minOffsetToTriggerRefresh) {
        [self sendActionsForControlEvents:UIControlEventValueChanged];
    }
}

In order for this action to correctly call a selector, the target and selector must be set. This is usually done right after initializing the custom pull-to-refresh.

[self.customRefreshControl addTarget:self
                              action:@selector(refresh)
                    forControlEvents:UIControlEventValueChanged];

The custom pull-to-refresh is now fully functional;  however, there is plenty of room for improvement, both visually and functionally. For example, you may want the custom pull-to-refresh to stick open while awaiting new data or have an animation that is based on the content offset of the containing scroll view. There is a simple addition that will make these embellishments easier. That is to add a containingScrollViewDidScroll: method that works in a similar manner to the containingScrollViewDidEndDragging: implemented earlier. Calling containingScrollViewDidScroll: from the implementation of [UIScrollViewDelegate scrollViewDidScroll:] allows the custom pull-to-refresh to keep track of whether or not it is scrolled into view, when it is scrolling, how much it has scrolled, etc.

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    [self.customRefreshControl containingScrollViewDidScroll:scrollView];
}

This should surmount the immediate technical challenges in getting a custom pull-to-refresh functional and provide a strong base upon which to build and get creative.

For future Insights like this, please subscribe to our newsletter at the bottom of the page (below the comments)!

Taylor Case

Taylor Case is a Software Engineer at Double Encore specializing in iOS Development. Taylor also enjoys cycling, pina coladas, and getting caught in the rain. Follow Taylor on Twitter or visit his website.

Article


Add your voice to the discussion: