10

The issue is the number of columns in the collectionView does NOT stay at 7 (the desired amount) upon rotation. What code change is required to fix this?

It seems the invalidateLayout from the custom UICollectionViewFlowLayout is NOT triggering the sizeForItemAtIndexPath method in the collectionView? Any ideas? I really just want the column resizing to occur via the sizeForItemAtIndexPath upon rotation.

NOTE: I'm not using storyboard here, rather I have a custom view in which I drop in a collectionView and reference my own collectionView classes.

The following code works fine, however upon rotation it does not keep the number of columns to 7 like it should. On running the code initially the console output shows:

GCCalendar - init coder
GCCalendar - commonInit
GCCalendarLayout:invalidateLayout
GCCalendarLayout:invalidateLayout
ViewController:viewWillLayoutSubviews
GCCalendarLayout:invalidateLayout
GCCalendarLayout:prepareLayout
sizeForItemAtIndexPath
.
.
sizeForItemAtIndexPath
minimumInteritemSpacingForSectionAtIndex
minimumLineSpacingForSectionAtIndex
GCCalendarLayout:collectionViewContentSize
GCCalendarLayout:layoutAttributesForElementsInRect
GCCalendarLayout:collectionViewContentSize
ViewController:viewWillLayoutSubviews
GCCalendarCell:drawRect
.
.
GCCalendarCell:drawRect

However then rotating the screen I see the following:

ViewController:viewWillLayoutSubviews
GCCalendarLayout:shouldInvalidateLayoutForBoundsChange
GCCalendarLayout:invalidateLayout
GCCalendarLayout:prepareLayout
GCCalendarLayout:collectionViewContentSize
GCCalendarLayout:layoutAttributesForElementsInRect
GCCalendarLayout:collectionViewContentSize

So the issue is "sizeForItemAtIndexPath" never got called????

Output Code on Rotation

Note: "sizeForItemAtIndexPath" is not triggered even though "invalidateLayout" is

ViewController:viewWillLayoutSubviews GCCalendarLayout:shouldInvalidateLayoutForBoundsChange GCCalendarLayout:invalidateLayout

**My Custom View that houses the collection View **

import UIKit

@IBDesignable class GCCalendarView: UIView {

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        commonInit()
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }



    // Private

    private func commonInit() {
        if self.subviews.count == 0 {
            let bundle = NSBundle(forClass: self.dynamicType)
            let nib = UINib(nibName: "GCCalendarView", bundle: bundle)
            let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
            view.frame = bounds
            view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
            addSubview(view)
        }
    }

}

Custom Collection View

import UIKit

class GCCalendar : UICollectionView, UICollectionViewDataSource, UICollectionViewDelegate {

    // Init ---------------

    func commonInit(coder aDecoder: NSCoder) {
        print("GCCalendar - commonInit")
        self.registerClass(GCCalendarCell.self, forCellWithReuseIdentifier: "GCCalendarCell")
        self.dataSource = self
        self.delegate = self

        let layout : GCCalendarLayout = GCCalendarLayout(coder: aDecoder)!
        self.setCollectionViewLayout(layout, animated: false)

        self.backgroundColor = UIColor.whiteColor()
    }

    required init?(coder aDecoder: NSCoder) {
        print("GCCalendar - init coder")
        super.init(coder: aDecoder)
        commonInit(coder: aDecoder)
    }


    // UICollectionViewDelegateFlowLayout ------------

    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
        print("sizeForItemAtIndexPath")
        let w : CGFloat = floor(self.frame.size.width/7)
        return CGSize(width: w, height: w)
    }

    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAtIndex section: Int) ->
        CGFloat {
        print("minimumInteritemSpacingForSectionAtIndex")
        return 0.0
    }

    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAtIndex section: Int) -> CGFloat {
        print("minimumLineSpacingForSectionAtIndex")
        return 0.0
    }


    // UICollectionViewDataSource -------------------

    func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 21
    }

    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier("GCCalendarCell", forIndexPath: indexPath) as? GCCalendarCell


        return cell!
    }
}

** Custom CollectionView Cell **

import UIKit

class GCCalendarCell: UICollectionViewCell {

    @IBOutlet weak var title : UITextField!
    @IBOutlet weak var date: UILabel!

    required init?(coder aDecoder: NSCoder) {
        print("GCCalendarCell - init:coder")
        super.init(coder: aDecoder)
        commonInit()
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }

    override func drawRect(rect: CGRect) {
        print("GCCalendarCell:drawRect")
        self.layer.borderWidth = 1
        self.layer.borderColor = UIColor.grayColor().CGColor
    }

    // Private

    private func commonInit() {
        let bundle = NSBundle(forClass: self.dynamicType)
        let nib = UINib(nibName: "GCCalendarCell", bundle: bundle)
        let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
        view.frame = bounds
        view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
        addSubview(view)
    }

}

Custom Layout

import UIKit

class GCCalendarLayout : UICollectionViewFlowLayout {
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
        print("GCCalendarLayout:shouldInvalidateLayoutForBoundsChange")
        return true
    }

    override func invalidateLayout() {
        print("GCCalendarLayout:invalidateLayout")
        super.invalidateLayout()

    }

    override func prepareForCollectionViewUpdates(updateItems: [UICollectionViewUpdateItem]) {
        print("GCCalendarLayout:prepareForCollectionViewUpdates")
        super.prepareForCollectionViewUpdates(updateItems)
    }

    override func finalizeCollectionViewUpdates() {
        print("GCCalendarLayout:finalizeCollectionViewUpdates")
        super.finalizeCollectionViewUpdates()
    }


}
Greg
  • 34,042
  • 79
  • 253
  • 454

4 Answers4

10

EDIT : Try this in your viewController :

override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) 
{
    (self.collectionview.collectionViewLayout).setItemSize(CGSizeMake(floor(size.width / 7), floor(size.width / 7)))
    self.collectionview.collectionViewLayout.invalidateLayout()
}
Kujey
  • 1,122
  • 6
  • 17
  • I must have this right as it does set the sizes correctly when running, it just at rotation change the issues arises – Greg Oct 19 '15 at 12:08
  • Probably yes. But still, could you add a log to the itemSize function of your custom flowlayout, just to see if it's called on rotation. – Kujey Oct 19 '15 at 12:38
  • sizeForItemAtIndexPath (if that's what you mean) isn't being called on rotation...that's the issue – Greg Oct 19 '15 at 20:34
1

if you are using Storyboard Set the CollectionviewFlowLayout Class In storyboard I attached Bellow

this is FlowLayout

Classname Set here

Ramkumar chintala
  • 958
  • 11
  • 24
1

Nothing helped for me from the existing answers (iPhone X Simulator iOS 11.2).
I've found that changing of estimatedSize helps for me with this problem:

<...>
//***new line
flowLayout.estimatedItemSize = [self getIconSize];//get a new size for items

[flowLayout invalidateLayout];
<...>
Andrew Romanov
  • 4,774
  • 3
  • 25
  • 40
0

@Kujey's solution can be applied to UIView without accessing UIViewController:

func traitCollectionDidChange(previousTraitCollection : UITraitCollection) {
    super.traitCollectionDidChange(previousTraitCollection)
    self.collectionView.collectionViewLayout.invalidateLayout()
}
k06a
  • 17,755
  • 10
  • 70
  • 110