Swift: How to refresh UICollectionView layout after rotation of the device

97,120

Solution 1

Add this function:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews() 
    myCollection.collectionViewLayout.invalidateLayout()
}

When you change the orientation, this function would be called.

Solution 2

The better option is to call invalidateLayout() instead of reloadData() because it will not force recreation of the cells, so performance will be slightly better:

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews() 
    myCollection.collectionViewLayout.invalidateLayout()
}

Solution 3

Also you can invalidate it in this way.

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

    [self.collectionView.collectionViewLayout invalidateLayout]; 
}

Solution 4

The viewWillLayoutSubviews() did not work for me. Neither did viewDidLayoutSubviews(). Both made the app go into an infinite loop which I checked using a print command.

One of the ways that do work is

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
// Reload here
}

Solution 5

When UICollectionLayout detects a bounds change, it asks if it needs to reroute the Invalidate layout. You can rewrite the method directly.UICollectionLayout can call invalidateLayout method at the right time

class CollectionViewFlowLayout: UICollectionViewFlowLayout{
    
    /// The default implementation of this method returns false.
    /// Subclasses can override it and return an appropriate value
    /// based on whether changes in the bounds of the collection
    /// view require changes to the layout of cells and supplementary views.
    /// If the bounds of the collection view change and this method returns true,
    /// the collection view invalidates the layout by calling the invalidateLayout(with:) method.
    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
        
        return (self.collectionView?.bounds ?? newBounds) != newBounds
    }
}
Share:
97,120

Related videos on Youtube

Talha Ahmad Khan
Author by

Talha Ahmad Khan

Updated on November 14, 2021

Comments

  • Talha Ahmad Khan
    Talha Ahmad Khan over 2 years

    I used UICollectionView (flowlayout) to build a simple layout. the width for each cell is set to the width of screen using self.view.frame.width

    but when I rotate the device, the cells don't get updated.

    enter image description here

    I have found a function, which is called upon orientation change :

    override func willRotateToInterfaceOrientation(toInterfaceOrientation: 
      UIInterfaceOrientation, duration: NSTimeInterval) {
        //code
    }
    

    but I am unable to find a way to update the UICollectionView layout

    The main code is here:

    class ViewController: UIViewController , UICollectionViewDelegate , UICollectionViewDataSource , UICollectionViewDelegateFlowLayout{
    
        @IBOutlet weak var myCollection: UICollectionView!
    
        var numOfItemsInSecOne: Int!
        override func viewDidLoad() {
            super.viewDidLoad()
    
            numOfItemsInSecOne = 8
            // Do any additional setup after loading the view, typically from a nib.
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
    
        override func willRotateToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) {
    
            //print("orientation Changed")
        }
    
        func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
            return 1
        }
    
        func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
            return numOfItemsInSecOne
        }
    
        func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
            let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cellO", forIndexPath: indexPath)
    
            return cell
        }
    
        func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize{
        let itemSize = CGSize(width: self.view.frame.width, height: 100)
        return itemSize
        }}
    
  • Talha Ahmad Khan
    Talha Ahmad Khan about 8 years
    thanks @khuong291 what if I have no object of my collectionview, and I have UICollectionViewController ? how will I suppose to call reloadData?
  • Khuong
    Khuong about 8 years
    UICollectionViewController already has collectionView property, so you just call them as I said. Just edit them into collectionView.reloadData(). It will work.
  • Rivera
    Rivera about 8 years
    So you need to reload the view, you can't just trigger a layout refresh?
  • thesummersign
    thesummersign about 7 years
    This is the correct way. This should be the accepted answer.
  • thesummersign
    thesummersign about 7 years
    check @kelin's answer .invalidateLayout() makes a lot lot of sense.
  • Abdul Waheed
    Abdul Waheed about 7 years
    this is the correct way to do it.. not the reload data again
  • nyxee
    nyxee almost 7 years
    that caused a crush for me. and an infinite loop recognized when i put a print statement in the viewDidLayoutSubviews function.
  • kelin
    kelin almost 7 years
    @nyxee, try viewWillLayoutSubviews then. I bet, your Collection View is the view of the View Controller? If so, I recommend to wrap it into another view.
  • nyxee
    nyxee almost 7 years
    thanks.. I'll get back to that at some point. I've switched to tableViews for now. I was using the UICollectionViewController.
  • Alexandre Cassagne
    Alexandre Cassagne over 6 years
    @kelin May I suggest you change your answer to viewWillLayoutSubviews? As it is, the layout doesn't update correctly in current iOS versions.
  • arionik
    arionik about 6 years
    This might cause endless loops. @birdy's answer works better
  • kelin
    kelin about 6 years
    The endless loop occurs if you use UICollectionViewController, because in this case the collectionView is also the view of the controller. So if you change collectionView layout viewWillLayoutSubviews and related methods will be called.
  • d4Rk
    d4Rk almost 6 years
    Don't forget to call super.viewWillTransition...
  • Rocket Garden
    Rocket Garden over 5 years
    This worked best for me, the approach required is really dependent of your application of CollectionView, in my instance this was more compatible with the other behaviours of my custom FlowLayout than the other solutions because my items all have the same size that is dependent on the traitCollection
  • jfgrang
    jfgrang over 5 years
    viewDidLayoutSubviews makes my app crash when going back to portrait because old layout cells are too big. viewWillLayoutSubviews works perfectly
  • Nikunj Kumbhani
    Nikunj Kumbhani over 5 years
    @Khuong it's work for me but some time cells are too big while going from landscape view to portrait. why happen like this?
  • Khuong
    Khuong over 5 years
    @NikunjKumbhani If so, I think you set layout wrong, please check it again.
  • Yuvraj Bashyal
    Yuvraj Bashyal over 5 years
    Thanks for your code but it didn't work in my case too but solved it using notification. Here is the way how it work on my case stackoverflow.com/questions/18653171/…
  • Hons
    Hons over 5 years
    I had to useAs @jfgrang proposed, i had to use viewWillLayoutSubviews to really get the height and width of the cells re-calculated properly... I'd however did not crash in either case.
  • DrWhat
    DrWhat about 5 years
    Brilliant - it also works when instantiating the UICollectionView within a custom UITableViewCell.
  • rv7284
    rv7284 almost 5 years
    app hangs on app launch after using this.
  • Imanou Petit
    Imanou Petit over 4 years
    This solution is not efficient as viewDidLayoutSubviews() is called many times when user scrolls the cells of the UICollectionView.
  • Dan Rosenstark
    Dan Rosenstark over 4 years
    This should be viewWillLayout
  • titusmagnus
    titusmagnus about 4 years
    I agree with @jfgrang: overriding viewWillLayoutSubviews is the only way I could get it to work (Xcode 11.4.1 + iOS 13.3).
  • Aidy
    Aidy almost 4 years
    viewWillTransitionToSize worked, viewDidLayoutSubviews hanged the app.
  • zumzum
    zumzum about 3 years
    I currently use viewWillTransitionToSize, but, does not seem as bullet proof as viewDidLayoutSubviews. viewDidLayoutSubviews kills performance though.
  • Lance Samaria
    Lance Samaria about 3 years
    I tried every other highly voted answer in this thread and this is the only one that worked. Btw my collectionView is a subView of the viewController(view.addSubview(myCollectionView) -added in viewDidLoad). I put a print statement inside viewWillLayoutSubviews and it only printed on rotations. I didn't have any endless loops.
  • Lance Samaria
    Lance Samaria about 3 years
    this is the same answer as @kelin's answer stackoverflow.com/a/42174492/4833705
  • Hamed Ghadirian
    Hamed Ghadirian almost 3 years
    TanX!!! You saved me hours! I change UIScreen.main.bounds.width to collectionView.frame.width and boom! Fixed!
  • Mehul
    Mehul almost 3 years
    Your Welcome @HamedGhadirian Enjoy 😉👍
  • tphduy
    tphduy over 2 years
    This solution doesn't work on iPad, because the horizontal trait and vertical trait are unchanged.
  • Lance Samaria
    Lance Samaria almost 2 years
    This answer worked for me last year and now it's not working in a new project. Very strange.