Calling setCollectionViewLayout:animated does not

2019-01-21 11:19发布

问题:

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.

回答1:

-setCollectionViewLayout: or -setCollectionViewLayout:animated: won't cause your UICollectionView reload.

  • If you want to change your cell style or update the data, call [self.collectionView reload] 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.

  • And I find this answer may give you help.
  • Also check the awesome video WWDC 2012 Advanced Collection Views and Building Custom Layouts


回答2:

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)


回答3:

You should call [self.tradeFeedCollectionView invalidateLayout]; before

if (self.changeLayout){
    [self.tradeFeedCollectionView setCollectionViewLayout:self.grideLayout animated:YES];
}
else {
    [self.tradeFeedCollectionView setCollectionViewLayout:self.tableViewLayout animated:YES];
}


回答4:

You should only have to call this method:

[self.collectionView setCollectionViewLayout:layout animated:YES];

I had a similar bug, but it ended up that my subviews of the cells were not setup with Auto Layout. The cell was resizing correctly, but my subviews were not, so it looked like the cell size never changed. Check this in your code quickly by setting clipsToBounds = YES on your cell.contentView.



回答5:

Here is the simple trick if you want to update layouts Use it wherever you are updating the collectionView:

[self.collectionView removeFromSuperview]; //First remove the Collection View

//Relocate the view with layouts
UICollectionViewFlowLayout *layout=[[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
layout.minimumInteritemSpacing = 10;
layout.minimumLineSpacing = 10;

 self.collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.width,100) collectionViewLayout:layout];
[self.collectionView setDataSource:self];
[self.collectionView setDelegate:self];
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cellIdentifier"];
[self.collectionView reloadData];

[self.view addSubview:self.collectionView];


回答6:

(Posted solution on behalf of the question author).

I finally found a nice and simple solution.

The marked answer was correct, that I needed to call, -reloadData on my UICollectionView - However, this destroyed the animation when switching between the two cells.

After taking in suggestions from the marked answer - I just reworked my `UIButton' method, that changes the cells

I found that if I call:

[self.collectionview performBatchUpdates:^{  } completion:^(BOOL finished) {

}];

According to the WDDC video - this animates the changes works with -reloadData method:

Example:

[self.collectionView reloadData]; 
    [self.collectionView performBatchUpdates:^{
        [self.collectionView.collectionViewLayout invalidateLayout];
        [self.collectionView setCollectionViewLayout:self.grideLayout animated:YES];
    } completion:^(BOOL finished) {

    }];
}


回答7:

I found that to easily and nicely animate both cells' kind and layout simultaneously it is best to reloadSections (instead of reloadData) followed by setting a new layout with animation:

[self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:0]];
[self.collectionView setCollectionViewLayout:newLayout animated:animated];