72

I want to implement pull-down-to-refresh in a UICollectionViewController under iOS 6. This was easy to achieve with a UITableViewController, like so:

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

The above implements a nice liquid-drop animation as part of a native widget.

As UICollectionViewController is a "more evolved" UITableViewController one would expect somewhat of a parity of features, but I can't find a reference anywhere to a built-in way to implement this.

  1. Is there a simple way to do this that I'm overlooking?
  2. Can UIRefreshControl be used somehow with UICollectionViewController despite the header and docs both stating that it's meant to be used with a table view?
Michael Celey
  • 12,087
  • 6
  • 53
  • 60
mjh
  • 3,468
  • 3
  • 16
  • 19

5 Answers5

215

The answers to both (1) and (2) are yes.

Simply add a UIRefreshControl instance as a subview of .collectionView and it just works.

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

That's it! I wish this had been mentioned in the documentation somewhere, even though sometimes a simple experiment does the trick.

EDIT: this solution won't work if the collection is not big enough to have an active scrollbar. If you add this statement,

self.collectionView.alwaysBounceVertical = YES;

then everything works perfectly. This fix taken from another post on the same topic (referenced in a comment in the other posted answer).

Community
  • 1
  • 1
mjh
  • 3,468
  • 3
  • 16
  • 19
  • 51
    I have to point out that UIRefreshControl.alloc.init is an abuse of dot notation. Dot notation is meant to convey accessing internal state, whereas bracket notation is meant to convey that you want an object to perform some action. – Peter Willsey Nov 14 '12 at 20:52
  • 1
    Think of it like lazy-loading where the internal state being accessed is an initialized instance. A stretch, but this is better than my initial response which was just going to be ":p". :) – mjh Nov 15 '12 at 01:37
  • If you're going to use dot notation here, I prefer UIRefreshControl.new – JLundell Apr 07 '13 at 18:17
  • I hadn't known about `new`, that's great. – mjh Apr 08 '13 at 20:07
  • How do you control when the spinner stops? I'm using this with a Parse app and the reloadData method inside the refershControlAction but the wheel just keeps spinning for ever. Any idea of what could be happening there? – Juan González Jul 07 '13 at 16:23
  • 2
    @JuanGonzález: Set the control to an iVar, ala `_refreshControl = blah`, then in `startRefresh:`, when the work is done, do a `_refreshControl endRefreshing];`. – Josh Jul 27 '13 at 02:49
  • Does not work in iOS7, the spinner doesn't show up. It's transparent it seems. – Van Du Tran Feb 18 '14 at 15:45
  • the bounce vertical (crucial) can also be set in IB. – mnl Oct 31 '14 at 18:40
  • 1
    If anyones interested and using Swift, I wrote a quick simple extension for UICollectionViewController for a convenience var for refreshControl. https://gist.github.com/Baza207/f5ff5abf7b8c44e2ffb3 – Baza207 Jan 27 '15 at 12:02
  • Please keep in mind, that adding a refreshControl via `addSubview:` may lead to unexpected behaviour. You can see one example when you use section headers (or supplementary views with the `UICollectionView`) - If you scroll while pull-to-refresh is active the insets of the headers are wrong (because they still use the loading indicator as reference). Apple makes some magic with the `refreshControl` property of `UITableView` - which is not available in a `UICollectionView` – orschaef Mar 21 '16 at 08:44
18

I was looking for the same solution, but in Swift. Based on the above answer, I have done the following:

let refreshCtrl = UIRefreshControl()
    ...
refreshCtrl.addTarget(self, action: "startRefresh", forControlEvents: .ValueChanged)
collectionView?.addSubview(refreshCtrl)

Not forgetting to:

refreshCtrl.endRefreshing()
Guy
  • 2,349
  • 1
  • 24
  • 35
djbp
  • 704
  • 8
  • 23
7

I was using Storyboard and setting self.collectionView.alwaysBounceVertical = YES; did not work. Selecting the Bounces and Bounces Vertically does the job for me.

enter image description here

harinsa
  • 2,946
  • 4
  • 26
  • 51
4

The refreshControl property has now been added to UIScrollView as of iOS 10 so you can set the refresh control directly on collection views.

https://developer.apple.com/reference/uikit/uiscrollview/2127691-refreshcontrol

UIRefreshControl *refreshControl = [UIRefreshControl new];
[refreshControl addTarget:self action:@selector(refreshControlAction:) forControlEvents:UIControlEventValueChanged];
self.collectionView.refreshControl = refreshControl;    
Berry Blue
  • 13,060
  • 16
  • 51
  • 91
2

mjh's answer is correct.

I ran into the issue where if the the collectionView.contentSize was not larger then the collectionView.frame.size, you can not get the collectionView to scroll. You can not set the contentSize property either (at least I couldn't).

If it can't scroll, it won't let you do the pull to refresh.

My solution was to subclass UICollectionViewFlowLayout and overide the method:

- (CGSize)collectionViewContentSize
{
    CGFloat height = [super collectionViewContentSize].height;

    // Always returns a contentSize larger then frame so it can scroll and UIRefreshControl will work
    if (height < self.collectionView.bounds.size.height) {
        height = self.collectionView.bounds.size.height + 1;
    }

    return CGSizeMake([super collectionViewContentSize].width, height);
}
Padin215
  • 7,266
  • 13
  • 58
  • 103
  • 13
    You can also do `self.collectionView.alwaysBounceVertical = YES;`. Credit goes to this [answer](http://stackoverflow.com/questions/14678727/uirefreshcontrol-on-uicollectionview-only-works-if-the-collection-fills-the-heig). – BFar May 11 '13 at 19:00