I am using a UICollectionView with two custom layouts - which are subclassed from UICollectionViewFlowLayout
When my view first loads I set it to a default view using:
[self.collectionView setCollectionViewLayout:self.tableViewLayout];
This calls: cellForItemAtIndexPath
- so no problem there. This is in viewWillAppear
method.
Then I use a UIButton to change the layout from current view to the next layout like so:
-(void)changeViewLayoutButtonPressed
{
self.changeLayout = !self.changeLayout;
if (self.changeLayout){
[self.tradeFeedCollectionView setCollectionViewLayout:self.grideLayout animated:YES];
}
else {
[self.tradeFeedCollectionView setCollectionViewLayout:self.tableViewLayout animated:YES];
}
}
After this call - numberOfItemsInSection
is called. I have checked in the debugger that the return account is not zero.
After that call, nothing else is called and my UICollectionView changes the layout but since cellForItemAtIndexPath
is not called - the cells are not setup correctly.
if I put [self.collectionView roloadData]
inside of numberOfItemsInSection
- then cellForItemAtIndexPath
is called and cells are setup.
I shouldn't need to do a manual call to reload the data in numberOfItemsInSection
. Can someone tell me whats going on?
Edit
I forgot to mention I am using two different UINibs for the two different cells as they need slightly different layouts applied to them. Would this be an issue? Thanks!
Edit 2
Right, so I have gotten it to work almost like I want it to. Here is all the code used for the collection view and how I change its layouts.
The Custom Layouts
These are subclassed from UICollectionViewFlowLayout
- The reason I subclassed is simply due to the face my UICollectionView
needs to look and behave very close to Apple's pre-made layout. Just different cell sizes, though.
TableViewLayout - No code in the header file. - BBTradeFeedTableViewLayout.m
@implementation BBTradeFeedTableViewLayout
-(id)init
{
self = [super init];
if (self){
self.itemSize = CGSizeMake(320, 80);
self.minimumLineSpacing = 0.1f;
}
return self;
}
@end
Gride Layout - No code in the header file. - BBTradeFeedGridViewLayout.m
@implementation BBTradeFeedGridViewLayout
-(id)init
{
self = [super init];
if (self){
self.itemSize = CGSizeMake(159, 200);
self.minimumInteritemSpacing = 0.1f;
self.minimumLineSpacing = 0.1f;
}
return self;
}
@end
Then in the ViewController
that has my UICollectionView
- I declare the two custom layouts like so:
@property (strong, nonatomic) BBTradeFeedTableViewLayout *tableViewLayout;
@property (strong, nonatomic) BBTradeFeedGridViewLayout *grideLayout;
I then register the two .xib files for the collectionView:
[self.tradeFeedCollectionView registerNib:[UINib nibWithNibName:@"BBItemTableViewCell" bundle:nil] forCellWithReuseIdentifier:@"TableItemCell"];
[self.tradeFeedCollectionView registerNib:[UINib nibWithNibName:@"BBItemGridViewCell" bundle:nil] forCellWithReuseIdentifier:@"GridItemCell"];
Then, I have a UIButton that changes the layout:
-(void)changeViewLayoutButtonPressed
{
if (!self.gridLayoutActive){
self.gridLayoutActive = YES;
[self.tradeFeedCollectionView setCollectionViewLayout:self.grideLayout animated:YES];
self.lastLayoutUsed = @"GridLayout";
}
else {
self.gridLayoutActive = NO;
[self.tradeFeedCollectionView setCollectionViewLayout:self.tableViewLayout animated:YES];
self.lastLayoutUsed = @"TableLayOut";
}
[self.tradeFeedCollectionView reloadItemsAtIndexPaths:[self.tradeFeedCollectionView indexPathsForVisibleItems]];
}
Finally - when a web service call is done - it calls [self.tradeFeedCollectionView reloadData];
So there my UICollectionView
gets reloaded and the last method:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{ static NSString *tableCellIdentifier = @"TableItemCell";
static NSString *gridCellIdentifier = @"GridItemCell";
if (indexPath.item < [self.tradeSearchArray count]){
if (self.gridLayoutActive == NO){
BBItemTableViewCell *tableItemCell = [collectionView dequeueReusableCellWithReuseIdentifier:tableCellIdentifier forIndexPath:indexPath];
if ([self.tradeSearchArray count] > 0){
self.toolBarButtomItem.title = [NSString stringWithFormat:@"%d Results", self.searchResult.searchResults];
tableItemCell.gridView = NO;
tableItemCell.backgroundColor = [UIColor whiteColor];
tableItemCell.item = self.tradeSearchArray[indexPath.row];
}
return tableItemCell;
}else
{
BBItemTableViewCell *gridItemCell= [collectionView dequeueReusableCellWithReuseIdentifier:gridCellIdentifier forIndexPath:indexPath];
if ([self.tradeSearchArray count] > 0){
self.toolBarButtomItem.title = [NSString stringWithFormat:@"%d Results", self.searchResult.searchResults];
gridItemCell.gridView = YES;
gridItemCell.backgroundColor = [UIColor whiteColor];
gridItemCell.item = self.tradeSearchArray[indexPath.row];
}
return gridItemCell;
}
The problem with the above code
The above code does work. However, the cells don't animate nicely and it looks odd, almost as if a refreshed happened. Which is due to calling:
[self.tradeFeedCollectionView reloadItemsAtIndexPaths:[self.tradeFeedCollectionView indexPathsForVisibleItems]];
But that is the only way I could get it to be close to what I want.
An object that manages an ordered collection of data items and presents them using customizable layouts.
An abstract base class for generating layout information for a collection view.
Overview. A flow layout is a type of collection view layout. Items in the collection view flow from one row or column (depending on the scrolling direction) to the next, with each row containing as many cells as will fit. Cells can be the same sizes or different sizes.
-setCollectionViewLayout:
or -setCollectionViewLayout:animated:
won't cause your UICollectionView
reload its data.
If you want to change your cell style or update the data, call [self.collectionView reloadData]
and UICollectionViewDataSource
protocol methods will be called.
If you want to change your UICollectionView
layout to another, call -setCollectionViewLayout:
. Or if you want to update your UICollectionView
layout, just call [self.collectionView.collectionViewLayout invalidateLayout]
.
Cell layout and data are two different things in UICollectionView
.
Update
you should discard all the stale data, probably by -reloadData
. And calculate the new frames for every cell in your UICollectionViewLayout subclasses. Override - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
and - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
.
You decide the new contentOffset
. For instance, you could keep the indexPath of some visible cell and decide to scroll to that indexPath
or setContentOffset:
to the frame.origin
of the new cell at the same indexPath
after your layout changed.
Simply call reloadData
before you switch your layout (the performBatchUpdates
call is not mandatory):
[self.collectionView reloadData];
[self.collectionView.collectionViewLayout invalidateLayout];
[self.collectionView setCollectionViewLayout:yourLayout animated:YES];
In Swift:
self.collectionView?.reloadData()
self.collectionView?.collectionViewLayout.invalidateLayout()
self.collectionView?.setCollectionViewLayout(yourLayout, animated: true)
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