Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can `UICollectionViewLayout` access data for invisible cells?

I'd like to fetch all data from a data source — both for visible and invisible cells — in order to calculate attributes for visible cells. collectionView:cellForItemAtIndexPath: doesn't work because it returns nil for invisible cells. As stated in the UICollectionView API:

Return Value

The cell object at the corresponding index path or nil if the cell is not visible or indexPath is out of range.

Any ideas on how I can fetch the underlying data without breaking MVC constraints (e.g., storing a reference to the data in the layout)?

like image 988
Gingi Avatar asked Apr 25 '14 17:04

Gingi


1 Answers

The main problem I'm facing is that I need information from the underlying data source to set up attributes of visible and non-visible cells, as well as supplementary views. The UICollectionViewDelegate protocol requires that cells are dequeued after the layout attributes have already been set, even though the implementing class (usually the UICollectionViewController has access to the data source).

The Collection View Programming Guide hints that the delegate object can extended to provide additional information. Using the UICollectionViewDelegateFlowLayout as a reference, I created a new protocol that declares the methods for the data I needed. My UICollectionViewController subclass then conformed to that protocol by implemented those methods, without any layout operations (like dequeuing view cells).

My protocol looks something like this:

@protocol MyCollectionViewDelegateLayout <UICollectionViewDelegate>
- (CGSize)sizeForCellAtIndexPath:(NSIndexPath *)indexPath;
- (CGSize)sizeForHeaderAtIndexPath:(NSIndexPath *)indexPath;
@end

My collection view controller then has the following structure:

@interface MyCollectionViewController : UICollectionViewController
    <MyCollectionViewDelegateLayout>
...
@end

@implementation MyCollectionViewController
...
- (CGSize)sizeForCellAtIndexPath:(NSIndexPath *)indexPath
{
    // Make calculations based on data at index path.
    return CGSizeMake(width, height);
}
- (CGSize)sizeForHeaderAtIndexPath:(NSIndexPath *)indexPath;
{
    // Make calculations based on data at index path.
    return CGSizeMake(width, height);
}

Inside the prepareLayout method in my UICollectionViewLayout subclass, I call these methods to pre-calculate the necessary attributes:

@implementation MyCollectionViewLayout
...
- (void)prepareLayout
{
    ...
    // Iterate over sections and rows...
    id dataSource = self.collectionView.dataSource;
    if ([dataSource respondsToSelector:@selector(sizeForCellAtIndexPath:)]) {
        CGSize size = [dataSource sizeForCellAtIndexPath:indexPath];
    } else {
        // Use default values or calculate size another way
    }
    ...
}
...

Using an extended data source protocol allows the code to maintain MVC Separation of Concerns. Here, the layout class still doesn't know anything about the underlying data, but is able to use it to define attributes. Conversely, the controller class does not lay anything out, instead only providing sizing hints based on the underlying data.

like image 73
Gingi Avatar answered Nov 04 '22 13:11

Gingi