3

using Xcode 7.0 beta, Swift 2.0, IOS 9 supported

I have a View Controller, which contains a Collection View. Inside is a header: Collection Reusable View, a collection: Collection View Cell and a footer: Collection Reusable View.

Inside the header, I have the functionality to login, register or logout. Depending on what button is clicked, Views inside this header will be hidden or not. Now the last thing I have to do is re-size the frame height of the header view (Collection Reusable View). Because when logged in, both RegisterView and LoginView will be hidden which will leave a large amount of empty space.

here is an image of my structure:

structure views

I quickly noticed that I'm unable to add any constraints to the Collection Reusable View :(

All the functionality for the header Collection Reusable View is inside a class that extends UICollectionReusableView. I'm able to read self.frame.height, but I cannot write to it. I seem to be able to edit the frame (why is height read-only, while frame is not, apple?). This however has no effect, even self.frame = CGRectZero doesn't do anything.

I noticed I can do this inside the main ViewController.swift, when initializing the header and footer (func collectionView, switch on kind -> UICollectionElementKindSectionHeader), but this is not what I want as it should change on button click.

For now, I move the RegisterView up (I edit the top constraint in the code).

CularBytes
  • 9,924
  • 8
  • 76
  • 101

2 Answers2

5

You can't directly manipulate the size of a view in a collection view. To control their sizes, your collection view's delegate should conform to UICollectionViewDelegateFlowLayout, then you can return whatever size you like for the header in that section by implementing the method collectionView(_:layout:referenceSizeForHeaderInSection:) on that protocol. If the header should not be visible, simply return 0.0for its height. Once you've changed the state of your view controller in a way that will require an update to this layout, call invalidateLayout() on the collection view's layout to trigger these layout methods on your delegate to be called.

class MyCollectionViewController : UICollectionViewController, UICollectionViewDelegateFlowLayout {

    var shouldHideHeader: Bool = false {
        didSet {
            self.collectionView?.collectionViewLayout.invalidateLayout()
        }
    }

    let defaultHeaderSize: CGFloat = 100.0
    let loginHeaderSectionIndex: Int = 0

    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {

        if self.shouldHideHeader && section == self.loginHeaderSectionIndex {
            return CGSize(width: collectionView.bounds.width, height: 0.0 )
        }
        else {
            return CGSize(width: collectionView.bounds.width, height: self.defaultHeaderSize)
        }
    }
}
Patrick Lynch
  • 2,742
  • 1
  • 16
  • 18
  • Since I have all my functionality in the header class, what is the best way to access the shouldHideHeader var or any other methods? – CularBytes Jul 10 '15 at 17:55
-1

Patrick gave the correct answer for changing the header height, I however, like I said in my question, had my header functionality in a different class, so I didn't really know how to access the real viewController class.

If you keep it simple, you can just create a action link from the storyboard to that class, I had some more operations to do, and not only on respond of a button click. This is what I did:

class MyViewController : UIViewController, UICollectionDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout{  

    var headerHeight: CGFloat = 100.0
    var shouldChangeHeader : Bool? = false
    let loginHeaderSectionIndex: Int = 0

   func setHeaderHeight(height : CGFloat!)
   {
       headerHeight = height
       shouldChangeHeader = true
       self.collectionView.collectionViewLayout.invalidateLayout()
   }

    func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
        switch kind
        {
        case UICollectionElementKindSectionHeader:
            let headerView = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: "loginHeader", forIndexPath: indexPath) as! LoginHeaderView
           //you should already have this method, LoginHeaderView is the class with all the functionality for the header, we register a callback.
            headerView.registerHeightChangeCallback(setHeaderHeight)
            return headerView
       }
}
    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
        //here we actually change the height, best is to include a if-condition, so it won't change on loading unless you especially call it.
        if self.shouldChangeHeader && self.loginHeaderSectionIndex == section {
            shouldChangeHeader = false
            return CGSize(width: collectionView.bounds.width, height: headerHeight )
        }
        else {
            //do nothing, keep as it is now.
            return CGSize(width: collectionView.bounds.width, height: collectionView.bounds.height) /
        }
    }
}

class LoginHeaderView : UICollectionReusableView {

    var heightCallback : ((height : CGFloat!)-> Void)?
    func registerHeightChangeCallback(callback :((height : CGFloat!)-> Void)?)
    {
        heightCallback = callback
    }
    @IBAction func buttonClicked( sender: AnyObject? ) { //or from any other method...

        if(heightCallback != nil)
        {
            heightCallback!(height: 200)
        }
    }
}
CularBytes
  • 9,924
  • 8
  • 76
  • 101