How do I define the size of a CollectionView on rotate

44,235

Solution 1

Heres my 2 cents - because your item sizes are static why don't you set the item size on the collectionViewLayout?

It will be quicker to do this rather than expecting the collection view to call its delegate method for every cell.

viewWillLayoutSubviews can be used for detection of rotation on view controllers. invalidateLayout can be used on a collection view layout to force it to prepare the layout again causing it to position the elements with the new sizes in this case.

- (void)viewWillLayoutSubviews;
{
    [super viewWillLayoutSubviews];
    UICollectionViewFlowLayout *flowLayout = (id)self.firstCollectionView.collectionViewLayout;

    if (UIInterfaceOrientationIsLandscape(UIApplication.sharedApplication.statusBarOrientation)) {
        flowLayout.itemSize = CGSizeMake(170.f, 170.f);
    } else {
        flowLayout.itemSize = CGSizeMake(192.f, 192.f);
    }

    [flowLayout invalidateLayout]; //force the elements to get laid out again with the new size
}

edit: updated with Swift2.0 example

override func viewWillLayoutSubviews() {
  super.viewWillLayoutSubviews()

  guard let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else {
    return
  }

  if UIInterfaceOrientationIsLandscape(UIApplication.sharedApplication().statusBarOrientation) {
    flowLayout.itemSize = CGSize(width: 170, height: 170)
  } else {
    flowLayout.itemSize = CGSize(width: 192, height: 192)
  }

  flowLayout.invalidateLayout()
}

Solution 2

With new API since iOS 8 I'm using:

Swift 3:

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)
    updateCollectionViewLayout(with: size)
}

private func updateCollectionViewLayout(with size: CGSize) {
    if let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
        layout.itemSize = (size.width < size.height) ? itemSizeForPortraitMode : itemSizeForLandscapeMode
        layout.invalidateLayout()
    }
}

Objective-C:

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
        [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
        [self updateCollectionViewLayoutWithSize:size];
}

- (void)updateCollectionViewLayoutWithSize:(CGSize)size {
        UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout;
        layout.itemSize = (size.width < size.height) ? itemSizeForPortraitMode : itemSizeForLandscapeMode;
        [layout invalidateLayout];
}

Solution 3

The Verified Answer Is Not Efficient

The Problem - Invalidating the layout in viewWillLayoutSubviews() is heavy work. viewWillLayoutSubviews() gets called multiple times when a ViewController is instantiated.

My Solution (Swift) - Embed your size manipulation within the UICollectionViewDelegateFlowLayout.

// Keep a local property that we will always update with the latest 
// view size.
var updatedSize: CGSize!

// Use the UICollectionViewDelegateFlowLayout to set the size of our        
// cells.
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
    // We will set our updateSize value if it's nil.
    if updateSize == nil {
        // At this point, the correct ViewController frame is set.
        self.updateSize = self.view.frame.size
    }

    // If your collectionView is full screen, you can use the 
    // frame size to judge whether you're in landscape.
    if self.updateSize.width > self.updateSize.height {
        return CGSize(width: 170, 170)
    } else {
        return CGSize(width: 192, 192)
    }
}

// Finally, update the size of the updateSize property, every time 
// viewWillTransitionToSize is called.  Then performBatchUpdates to
// adjust our layout.
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
    self.updateSize = size
    self.collectionView!.performBatchUpdates(nil, completion: nil)
}

Solution 4

You can change your if statement. You will need to have a reference to the collection views

if (UIDeviceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation]) && collectionView == self.firstCollectionView) {
   return CGSizeMake(170.f, 170.f);
}

Solution 5

The question was , how to separate two collection views in sizeForItemAtIndexPath. Why just not something like this?

 func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {

    if collectionView == firstCollectionView{

            return CGSize(width: self.frame.width/3, height: 40)
       }


    else if collectionView == secondCollectionView{

        return CGSize(width: self.frame.width, height: self.frame.height-2)
    }

    abort()
}
Share:
44,235
Robert Farr
Author by

Robert Farr

Updated on July 09, 2022

Comments

  • Robert Farr
    Robert Farr almost 2 years

    I have a viewcontroller with 2 CollectionView Controllers.

    I would like on rotation that only one of the collection views resize to a custom size while the other remains the same.

    I have tried to use:

    - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout  *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
      {    
         // Adjust cell size for orientation
         if (UIDeviceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation])) {
            return CGSizeMake(170.f, 170.f);
         }
        return CGSizeMake(192.f, 192.f);
    }
    

    However that just changes both collectionviews. How do I get it to be specific to only one collection?

  • Robert Farr
    Robert Farr over 10 years
    The code I posted works fine for resizing, however I want it to only resize one of the 2 seperate collection views
  • Robert Farr
    Robert Farr over 10 years
    This works however I am now getting a warning that states: Incompatible pointer types initializing 'UICollectionViewFlowLayout *' with an expression of type 'UICollectionViewLayout *'
  • Oliver Atkinson
    Oliver Atkinson over 10 years
    you will need to cast the collectionViewLayout, i will adjust the answer now @RobertFarr
  • progrmr
    progrmr over 10 years
    Why don't you just look at the collectionView: parameter to see which one is being passed in and only apply it when its the right one??
  • TomSawyer
    TomSawyer over 9 years
    @OliverAtkinson tks. it's already worked with my app. but what is happen when in each cell, i have 1 imageview? after resize collection view cell, but image did'nt load, i have to reload or swipe left / right to get image update. how can i update/ reload these imageview ?
  • Oliver Atkinson
    Oliver Atkinson over 9 years
    @TomSawyer you can use reloadItemsAtIndexPath if the data has changed - but rotation should not affect the underlying data and should not require a reload of the data if it is already there.
  • TomSawyer
    TomSawyer over 9 years
    @OliverAtkinson I've changed itemSize after rotate but imageview in cell will not fit with new itemSize. Here is screenshot: i.stack.imgur.com/dEjY8.png , so, i have to swipe left / right to update imageView or use collectionView.reloadData() to get imageView fit with new cell itemSize like this i.stack.imgur.com/XB9AA.jpg . Any better way to solve it , i heard about constraint ?
  • Oliver Atkinson
    Oliver Atkinson over 9 years
    Are you using autolayout? make the imageView leading and trailing constraints to the superview - or if not turn all its autoresizing masks on and make it the full size of the cell
  • Gene De Lisa
    Gene De Lisa about 8 years
    Instead of saving the size and calling performBatchUpdates, all you need to do is call self.collectionView!.collectionViewLayout.invalidateLayout() Then in sizeForItemAtIndexPath simply get the new bounds from the view/collectionView
  • Pushpa Y
    Pushpa Y almost 6 years
    Thanks. Worked for me.