Episode #24

Pull to Refresh

19 minutes
Published on July 12, 2012

This video is only available to subscribers. Get access to this video and 582 others.

In this episode, I cover how to implement Pull to Refresh on UITableView using an easy open source project called SSPullToRefresh, by Sam Soffes. I cover the basics, as well as creating a custom loading panel, drawn with Core Graphics.

Links

Adding SSPullToRefresh to a UITableView

First, you need to import the header and make your view controller conform to the delegate:

#import "SSPullToRefresh.h"

@interface MasterViewController : UITableViewController <SSPullToRefreshViewDelegate>

@end

Then declare a property for the component. This can be done in the header or in the implementation if you're using a class continuation.

@property (nonatomic, strong) SSPullToRefreshView *pullToRefreshView;

Then, in viewDidLoad...

    self.pullToRefreshView = [[SSPullToRefreshView alloc] 
                                          initWithScrollView:self.tableView
                                                    delegate:self];

Finally, implement some delegate methods:

- (BOOL)pullToRefreshViewShouldStartLoading:(SSPullToRefreshView *)view {
    return YES;
}

- (void)pullToRefreshViewDidStartLoading:(SSPullToRefreshView *)view {
    [self refresh];
}

- (void)pullToRefreshViewDidFinishLoading:(SSPullToRefreshView *)view {
}

And in our sample, we fake the network delay by using dispatch_after in the refresh method.

    double delayInSeconds = 2.0;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
    dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_after(popTime, backgroundQueue, ^(void){
        [_objects removeAllObjects];
        for (int i = 0; i < 25; i++) {
            [self insertNewObject:nil];
        }

        dispatch_async(dispatch_get_main_queue(), ^{
            [self.pullToRefreshView finishLoading];
            [self.tableView reloadData];
        });
    });

Creating a custom content view

#import "SSPullToRefresh.h"

@interface CustomPullToRefreshView : UIView <SSPullToRefreshContentView>

@end

The custom drawing of the 2 rectangles, based on the progress %:

- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    [[UIColor whiteColor] set];
    CGContextFillRect(context, self.bounds);

    CGFloat width = self.bounds.size.width;
    CGFloat height = self.bounds.size.height;
    CGFloat barHeight = 10;
    CGFloat barWidth = (width / 2.0) * _progress;

    CGFloat barY = height - barHeight;

    CGRect leftRect = CGRectMake(0, barY, barWidth, barHeight);
    [_barColor set];
    CGContextFillRect(context, leftRect);

    CGFloat rightX = width - barWidth;
    CGRect rightRect = CGRectMake(rightX, barY, barWidth, barHeight);
    CGContextFillRect(context, rightRect);
}

Next, make sure you conform to the SSPullToRefreshContentView protocol methods:

- (void)setState:(SSPullToRefreshViewState)state withPullToRefreshView:(SSPullToRefreshView *)view {
    switch (state) {
        case SSPullToRefreshViewStateNormal:
            _barColor = [UIColor lightGrayColor];
            break;

        case SSPullToRefreshViewStateLoading:
            [_indicator startAnimating];
            _barColor = [UIColor colorWithRed:.7 green:1.0 blue:.7 alpha:1.0];
            break;

        case SSPullToRefreshViewStateReady:
            _barColor = [UIColor greenColor];
            break;

        case SSPullToRefreshViewStateClosing:
            [_indicator stopAnimating];
            _barColor = [UIColor whiteColor];
            break;

        default:
            break;
    }
}

- (void)setPullProgress:(CGFloat)pullProgress {
    _progress = pullProgress;
    [self setNeedsDisplay];
}

Then wire it all up:

self.pullToRefreshView.contentView = [[CustomPullToRefreshView alloc] init];