1

I'm swapping out the data being displayed in my collection view by changing the datasource. This is being done as part of a tab-like interface. When the new data loads, I would like to flash the scroll indicators to tell the user that there's more data outside of the viewport.

Immediately

Doing so immediately doesn't work because the collection view hasn't loaded the data yet:

collectionView.dataSource = dataSource2;
[collectionView flashScrollIndicators]; // dataSource2 isn't loaded yet

dispatch_async

Dispatching the flashScrollIndicators call later doesn't work either:

collectionView.dataSource = dataSource2;
dispatch_async(dispatch_get_main_queue(), ^{
    [collectionView flashScrollIndicators]; // dataSource2 still isn't loaded
});

performSelector:withObject:afterDelay:

Executing the flashScrollIndicators after a timed delay does work (I saw it somewhere else on SO), but leads to a bit of lag with the scroll indicators being shown. I could decrease the delay, but it seems like it'll just leads to a race condition:

collectionView.dataSource = dataSource2;
[collectionView performSelector:@selector(flashScrollIndicators) withObject:nil afterDelay:0.5];

Is there a callback that I can hook on to to flash the scroll indicators as soon as the collection view has picked up on the new data and resized the content view?

Edward Dale
  • 29,597
  • 13
  • 90
  • 129
  • 1
    Did you try calling `[collectionView reloadData]` after setting the new data source and before sending `flashScrollIndicators`? – rob mayoff Jun 16 '13 at 23:37
  • You could perhaps try using a block [like this](http://stackoverflow.com/a/10817187/1367622) to reload the data and call `flashScrollIndicators` on completion. – Steph Sharp Jun 17 '13 at 00:36
  • Calling `reloadData` immediately before `flashScrollIndicators` also doesn't work, as mentioned by [zvjerka24](http://stackoverflow.com/questions/1483581/get-notified-when-uitableview-has-finished-asking-for-data/10817187#comment20638630_10817187). – Edward Dale Jun 17 '13 at 18:30
  • You said the calling `-flashScrollIndicators` does work when using `performSelector:withObject:afterDelay:` but it lags a bit. Have you tried with a delay of 0? A delay of 0 still delays the message a bit. – Gianluca Tranchedone Jun 18 '13 at 08:39
  • That also doesn't work – Edward Dale Jun 18 '13 at 17:51

3 Answers3

3

Put your call to flashScrollIndicators inside UICollectionViewLayout's method -finalizeCollectionViewUpdates.

From Apple's documentation:

"... This method is called within the animation block used to perform all of the insertion, deletion, and move animations so you can create additional animations using this method as needed. Otherwise, you can use it to perform any last minute tasks associated with managing your layout object’s state information."

Hope this helps!

Edit:

Ok, I got it. Since you mentioned the finalizeCollectionViewUpdates method was not being called I decided to try it myself. And you're right. The problem is (sorry I didn't notice this earlier) that method is only called after you update the Collection View (insert, delete, move a cell, for example). So in this case it doesn't work for you. So, I have a new solution; it involves using UICollectionView's method indexPathsForVisibleItems inside UICollectionViewDataSource's method collectionView:cellForItemAtIndexPath:

Every time you hand a new UICollectionViewCell to your collection view, check if it is the last of the visible cells by using [[self.collectionView indexPathsForVisibleItems] lastObject]. You will also need a BOOL ivar to decide if you should flash the indicators. Every time you change your dataSource set the flag to YES.

- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewCell *cell = [cv dequeueReusableCellWithReuseIdentifier:@"MyCell" forIndexPath:indexPath];
    cell.backgroundColor = [UIColor whiteColor];

    NSIndexPath *iP = [[self.collectionView indexPathsForVisibleItems] lastObject];
    if (iP.section == indexPath.section && iP.row == indexPath.row && self.flashScrollIndicators) {
        self.flashScrollIndicators = NO;
        [self.collectionView flashScrollIndicators];
    }

    return cell;
}

I tried this approach and it's working for me.

Hope it helps!

LuisCien
  • 6,362
  • 4
  • 34
  • 42
  • Great find, but that method doesn't appear to be called when changing the dataSource. – Edward Dale Jun 19 '13 at 04:54
  • Just changing the 'dataSource' property won't cause the UICollectionView to reload the data. To get that method called try calling the 'reloadData' method after changing the dataSource: `collectionView.dataSource = dataSource2;` `[collectionView reloadData];` – LuisCien Jun 19 '13 at 05:03
  • It looks like the method still isn't being called when I call `reloadData`. – Edward Dale Jun 19 '13 at 05:55
  • Thanks for the answer, but I think the accepted answer is a bit more direct. – Edward Dale Jun 20 '13 at 03:32
3

Subclassing UICollectionView and overriding layoutSubviews can be a solution. You can call [self flashScrollIndicators] on the collection. Problem is that layoutSubviews gets called in multiple scenarios.

  1. Initially when collection is created and datasource is assigned.
  2. On scrolling, cells which go beyond the viewport get re-used & re-layout.
  3. Explicitly change frame/reload the collection.

Workaround to this can be, keeping a BOOL property which will be made YES only when reloading datasource, otherwise will remain NO. Thus flashing of scroll bars will happen explicitly only when reloading collection.

In terms of source code,

MyCollection.h

#import <UIKit/UIKit.h>

@interface MyCollection : UICollectionView

@property (nonatomic,assign) BOOL reloadFlag;

@end

MyCollection.m

#import "MyCollection.h"

@implementation MyCollection

- (void) layoutSubviews {
    [super layoutSubviews];
    if(_reloadFlag) {
        [self flashScrollIndicators];
        _reloadFlag=NO;
    }
}

Usage should be

self.collection.reloadFlag = YES;
self.collection.dataSource = self;
Amar
  • 13,202
  • 7
  • 53
  • 71
0

create your own collection view class like @Amar suggested, and overwrite both layoutSubviews and reloadData like this

class FlashScrollBarCollectionView: UICollectionView {

   var flashScrollBar = false
   override func layoutSubviews() {
       super.layoutSubviews()
       if flashScrollBar {
           flashScrollIndicators()
           flashScrollBar = false
       }
   }

   override func reloadData() {
       flashScrollBar = true
       super.reloadData()
   }
}
Maria
  • 4,471
  • 1
  • 25
  • 26