30

I am implementing UIRefreshControl on a UITableView to refresh the table's data. On other pull-to-refresh implementations, the refresh process does not begin until the user's finger is lifted while in the pull's refresh distance. UIRefreshControl does not immediately seem like it has this customization.

My UIRefreshControl init code:

UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self action:@selector(refresh:) forControlEvents:UIControlEventValueChanged];
[self.tableView addSubview:refreshControl];

My refresh: code is fairly basic:

- (void)refresh:(id)sender {
    // Refresh code...
    [sender endRefreshing];
}

How can I delay the refresh: function until the user removes their finger from the pull?

HangarRash
  • 7,314
  • 5
  • 5
  • 32
outphase
  • 303
  • 1
  • 3
  • 4

3 Answers3

39

I've also stuck with the same problem. I don't think that my approach is very nice, but seems like it works.

  1. Init UIRefreshControl

    UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
    self.refreshControl = refreshControl;
    
  2. Check state of UIRefreshControl when user finish dragging the table (UITableViewDelegate conforms to UIScrollViewDelegate)

    - (void)scrollViewDidEndDecelerating:(UIScrollView*)scrollView
    {    
        if( self.refreshControl.isRefreshing )
            [self refresh];
    }
    
  3. Update table

    - (void)refresh
    {
        [self.refreshControl endRefreshing];
    
        // TODO: Update here your items
    
        [self.tableView reloadData];
    }
    

Hope it will help you.

Scott Berrevoets
  • 16,921
  • 6
  • 59
  • 80
Ned
  • 1,378
  • 16
  • 28
  • 1
    This solution works if the user slowly does the drag and pull. If it's almost like a flick, the loading wheel will run but the refresh: function won't be called until the table is moved again. edit: I changed the function to scrollViewDidEndDecelerating: and it seems to work fine now. Thanks! – outphase Mar 24 '13 at 00:21
  • 1
    Yes, you are quite right! Now it works much better, thanks for correcting my code!) – Ned Mar 24 '13 at 18:16
  • 1
    There is a slight issue with this. If you pull the table view down enough to refresh, keep your finger down, go back up to where the spinner is roughly half visible, then let go...it will show the spinner spinning and nothing will happen. – Cameron Askew Jun 16 '14 at 18:05
  • @CameronAskew, this behaviour can be fixed by calling the same code as in `scrollViewDidEndDecelerating` also inside of the `scrollViewDidEndDragging` method. But in this case we will also have to close refresh control by calling `setContentOffset(CGPoint.zero, animated: true)` of our `UIScrollView` or `UITableView`. – Andrei K. Dec 07 '17 at 14:28
20

UIRefreshControl already has accommodations for starting at the "right" time. The correct behavior for a pull-to-refresh control is to start the refresh after the user has crossed some "far enough" threshold, not when the user has released their drag.

In order to accomplish this, you need to modify your -refresh: method to check for when the control has transitioned into the refreshing state:

-(void)refresh:(id)sender {
    UIRefreshControl *refreshControl = (UIRefreshControl *)sender;
    if(refreshControl.refreshing) {
        (... refresh code ...)
    }
}

Note that any method you call for your (... refresh code ...) should be asynchronous, so that your UI doesn't freeze. You should change to main queue and call -endRefreshing at the end of your (... refresh code ...) block, instead of at the end of -refresh::

- (void)refresh:(id)sender {
    __weak UIRefreshControl *refreshControl = (UIRefreshControl *)sender;
    if(refreshControl.refreshing) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            /* (... refresh code ...) */
            dispatch_sync(dispatch_get_main_queue(), ^{
                [refreshControl endRefreshing];
                //reload the table here, too
            });
        });
    }
}

Changing the control event to UIControlEventTouchUpInside will not work because UIRefreshControl is not a UI component intended to be directly interacted with. The user will never touch the UIRefreshControl, so no UIControlEventTouchUpInside event can be triggered.

  • it's not working. refresh method only called once, before the user lift his finger (tested with ios 10.2) – user1105951 Jan 12 '17 at 18:46
  • The behavior of `UIRefreshControl` has changed in the past 5 years. The "normal" implementation (what was described in the question) now behaves correctly, assuming the work of refreshing does not block the main thread. This is the behavior that matches system refresh controls (e.x. the one in Mail). Beginning refresh in `-scrollViewDidEndDecelerating:` is not. –  Nov 25 '18 at 06:35
5

Scheduling the refresh to only occur when the finger has lifted can be achieved by using NSRunLoop. Call -(void)performInModes:(NSArray<NSRunLoopMode> *)modes block:(void (^)(void))block; with NSRunLoopDefaultMode in the array.

While the touch is still held, the runloop mode is UITrackingRunLoopMode, it will only go back to the default after liftoff.

DrMickeyLauer
  • 4,455
  • 3
  • 31
  • 67