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)?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With