31

In my application I use refresh control with collection view.

UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:[UIScreen mainScreen].bounds];
collectionView.alwaysBounceVertical = YES;
...
[self.view addSubview:collectionView];

UIRefreshControl *refreshControl = [UIRefreshControl new];
[collectionView addSubview:refreshControl];

iOS7 has some nasty bug that when you pull collection view down and don't release your finger when refreshing begins, vertical contentOffset shifts for 20-30 points down which results in ugly scroll jump.

Tables have this problem too if you use them with refresh control outside of UITableViewController. But for them it could be easily solved by assigning your UIRefreshControl instance to UITableView's private property called _refreshControl:

@interface UITableView ()
- (void)_setRefreshControl:(UIRefreshControl *)refreshControl;
@end

...

UITableView *tableView = [[UITableView alloc] initWithFrame:[UIScreen mainScreen].bounds];
[self.view addSubview:tableView];

UIRefreshControl *refreshControl = [UIRefreshControl new];
[tableView addSubview:refreshControl];
[tableView _setRefreshControl:refreshControl];

But UICollectionView does not have such property so there must be some way to deal with it manually.

Ilya Laktyushin
  • 380
  • 1
  • 3
  • 8

2 Answers2

52

Having the same problem and found a workaround that seems to fix it.

This seems to be happening because the UIScrollView is slowing down the tracking of the pan gesture when you pull past the edge of the scrollview. However, UIScrollView is not accounting for changes to contentInset during tracking. UIRefreshControl changes contentInset when it activates, and this change is causing the jump.

Overriding setContentInset on your UICollectionView and accounting for this case seems to help:

- (void)setContentInset:(UIEdgeInsets)contentInset {
  if (self.tracking) {
    CGFloat diff = contentInset.top - self.contentInset.top;
    CGPoint translation = [self.panGestureRecognizer translationInView:self];
    translation.y -= diff * 3.0 / 2.0;
    [self.panGestureRecognizer setTranslation:translation inView:self];
  }
  [super setContentInset:contentInset];
}

Interestingly, UITableView accounts for this by NOT slowing down tracking until you pull PAST the refresh control. However, I don't see a way that this behavior is exposed.

Meet Doshi
  • 4,241
  • 10
  • 40
  • 81
Tim Norman
  • 1,999
  • 1
  • 20
  • 26
  • Well, that certainly makes the problem a lot less apparent. Could you tell me how you arrived at this calculation? It doesn't appear to be quite spot on.On my phone I get the best results when I multiply by 1.7 instead of 3/2. But 1.7 feels even more arbitrary than 3/2. It would be nice to find out the exact adjustment that is needed... – Johan Levin Nov 12 '13 at 11:49
  • After some more experimenting it looks like you are right about 3/2. But where does that fraction come from??? – Johan Levin Nov 12 '13 at 12:27
  • I determined it empirically by looking at the ratio between the gesture recognizer's translation value and the difference moved by the scrollview when scrolling past the contentInset point. It is not perfect, and I think on iOS 6 it's a different value. – Tim Norman Nov 12 '13 at 16:53
  • Is it relevant that Apple says UIRefreshControl is guaranteed to work only with UITableViews? – rounak Dec 31 '13 at 19:01
  • 4
    Works perfectly for me! But I noticed that this solution is good for 4" display. For 3,5" there is a slight jump. Fixed with a bit junky but working fix: `translation.y -= diff * (screenHeight < 568. ? 3.5 : 3.0) / 2.0;` – Anton Ogarkov Jan 03 '14 at 13:11
  • This is also working on iOS 8 with the 4.7" iPhone 6 – cynistersix Dec 17 '14 at 04:28
  • Hey, did you ever solve this behavior for UIWebViews? They are not direct subclasses of UIScrollView but they do have one inside their properties... – Pochi Dec 28 '16 at 00:53
  • Unfortunately this doesn't seem to work in newer iOS releases. Even though `UIRefreshControl` is said to be supported in every `UIScrollView` descendant, it looks like it only works in its *comfort situation*, i.e. when it's filling the whole screen. It _jumps_ for me over 80px when using in a `UITableView` that doesn't fill the whole screen. – DrMickeyLauer Oct 11 '18 at 19:02
  • Is this still an issue as of iOS 11 ? – Petar May 07 '20 at 17:25
1
- (void)viewDidLoad
{
     [super viewDidLoad];

     self.refreshControl = [[UIRefreshControl alloc] init];
     [self.refreshControl addTarget:self action:@selector(scrollRefresh:) forControlEvents:UIControlEventValueChanged];
     [self.collection insertSubview:self.refreshControl atIndex:0];
     self.refreshControl.layer.zPosition = -1;
     self.collection.alwaysBounceVertical = YES;
 }

 - (void)scrollRefresh:(UIRefreshControl *)refreshControl
 {
     self.refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:@"Refresh now"];
     // ... update datasource
     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        self.refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"Updated %@", [NSDate date]]];
        [self.refreshControl endRefreshing];
        [self.collection reloadData];
     }); 

 }
Roman Solodyashkin
  • 799
  • 12
  • 17