Understanding UICollectionView Headers

Asked 2 years ago, Updated 2 years ago, 45 views

Using UICollectionView, we implement screens with multiple sections.
We use UICollectionViewFlowLayout customized to make cell sizes variable.

To display the header, the recognition is to set the following values when you generate the layout:

let layout=UICollectionViewFlowLayout()
layout.headerReferenceSize=CGSizeMake(10,10)

By configuring the above, the following functions are known:

func collectionView(collectionView:UICollectionView, viewForSupplementaryElementOfKind:String, atIndexPathindexPath:NSIndexPath)->UICollectionReusableView{}

When using custom classes for layout as follows, the above functions are no longer called.

let layout=CustomLayout()
layout.headerReferenceSize=CGSizeMake(10,10)

Is there any reason why the headerReferenceSize value has been cleared somewhere?

ViewController.swift

 var myCollectionView:UICollectionView!

var mySection: [String] = ["Section 1", "Section 2" ]

override func viewDidLoad(){
    super.viewDidLoad()

    let layout = CustomLayout()

    // Header Size by Section
    layout.headerReferenceSize=CGSizeMake(self.view.frame.width,100)

    layout.sectionInset=UIedgeInsetsMake(8,8,8,8)

    layout.minimumLineSpacing=8
    layout.minimumInteritemSpacing=8

    layout.maxColumn = 5
    layout.cellPattern.append ((sideLength:2, heightLength:2, column:0, row:0))
    layout.cellPattern.append ((sideLength:1, heightLength:1, column:2, row:0))
    layout.cellPattern.append ((sideLength:1, heightLength:1, column:3, row:0))
    layout.cellPattern.append ((sideLength:1, heightLength:1, column:4, row:0))
    layout.cellPattern.append ((sideLength:1, heightLength:1, column:2, row:1))
    layout.cellPattern.append ((sideLength:1, heightLength:1, column:3, row:1))
    layout.cellPattern.append ((sideLength:1, heightLength:1, column:4, row:1))

    myCollectionView=UICollectionView(frame:self.view.frame, collectionViewLayout:layout)

    myCollectionView.registerClass(CustomUICollectionViewCell.self, forCellWithReuseIdentifier: "MyCell")
    myCollectionView.registerClass(CustomCollectionReusableView.self, forSupplementaryViewOfKind:UICollectionElementKindSectionHeader, withReuseIdentifier: "MySection")

    myCollectionView.delegate=self
    myCollectionView.dataSource=self

    self.view.addSubview(myCollectionView)

}

override funcdidReceiveMemoryWarning(){
    super.didReceiveMemoryWarning()
}

func numberOfSectionsInCollectionView (collectionView:UICollectionView) - > Int {
    return mySection.count
}

functioncollectionView(collectionView:UICollectionView, didSelectItemAtIndexPathindexPath:NSIndexPath){
    print ("Cell Press")
}

functioncollectionView (collectionView:UICollectionView, numberOfItemsInSection section:Int) - > Int {
    switch(section){
    case0:
        return7
    case1:
        return7
    default:
        return 0
    }
}

func collectionView (collectionView:UICollectionView, viewForSupplementaryElementOfKindkind: String, atIndexPathindexPath:NSIndexPath)->UICollectionReusableView{

    letheaderView: CustomCollectionReusableView=collectionView.dequeueReusableSupplementaryViewOfKind(UICollectionElementKindSectionHeader, withReuseIdentifier: "MySection", forIndexPath:indexPath) as!CustomCollectionReusableView

    headerView.title?.text=mySection [indexPath.section]

    return headerView

}

functioncollectionView (collectionView:UICollectionView, cellForItemAtIndexPathindexPath:NSIndexPath) - >UICollectionViewCell{
    letcell:CustomUICollectionViewCell=collectionView.dequeueReusableCellWithReuseIdentifier("MyCell", forIndexPath:indexPath)as!CustomUICollectionViewCell

    switch(indexPath.section){
    case0:
        cell.backgroundColor=UIColor.redColor()
        cell.textLabel?.text="0"
        cell.imageTest.image=UIImage(named: "image01.jpg")
    case1:
        cell.backgroundColor=UIColor.greenColor()
        cell.textLabel?.text="1"
    default:
        print("section error")
        cell.backgroundColor=UIColor.whiteColor()
    }
    return cell
}

CustomUICollectionViewCell.swift

var textLabel:UILabel!
varimageTest —UIImageView!

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

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

    textLabel = UILabel (frame: CGRectMake (0,0, frame.width, frame.height))
    textLabel?.text="nil"
    textLabel?.textAlignment=NSTextAlignment.Center

    imageTest = UIImageView (frame: CGRectMake(0,0, frame.width, frame.height))

    self.contentView.addSubview(imageTest)
    self.contentView.addSubview(textLabel)
}

CustomLayout.swift

private staticlet maxRow=3

var maxColumn = maxRow
varcellPattern: [(sideLength: CGFloat, heightLength: CGFloat, column: CGFloat, row: CGFloat)] = [ ]

private var sectionCells=[[CGRect]]()
private var contentSize = CGSizeZero

override func prepareLayout() {
    super.prepareLayout()

    sectionCells=[CGRect]()

    iflet collectionView =self.collectionView {
        contentSize = CGSize (width: collectionView.bounds.width-collectionView.contentInset.left-collectionView.contentInset.right, height:0)
        let smallCellSideLength: CGFloat=(contentSize.width-super.sectionInset.left-super.sectionInset.right-(super.minimumInteritemSpacing*(CGFloat(maxColumn)-1.0))/ CGFloat(maxColumn)

        for section in (0..<collectionView.numberOfSections()) {
            varcells= [CGRect]()
            let numberOfCellInSection=collectionView.numberOfItemsInSection(section)
            var height = contentSize.height

            for in(0..<numberOfCellInSection){
                let position = i% (numberOfCellInSection)
                let cellPosition=position%cellPattern.count
                letcell=cellPattern [cellPosition]
                letx=(cell.column*(smallCellSideLength+super.minimumInteritemSpacing)) + super.sectionInset.left
                letty=(cell.row*(smallCellSideLength+super.minimumLineSpacing))+contentSize.height+super.sectionInset.top
                let cellwidth=(cell.sideLength*smallCellSideLength)+(cell.sideLength-1)*super.minimumInteritemSpacing)
                let cellheight=(cell.heightLength*smallCellSideLength)+(cell.heightLength-1)*super.minimumInteritemSpacing)

                let cellRect=CGRectMake(x,y,cellwidth,cellheight)
                cells.append(cellRect)

                if(height<cellRect.origin.y+cellRect.height){
                    height = cellRect.origin.y + cellRect.height
                }
            }
            contentSize = CGSize (width: contentSize.width, height:height)
            sectionCells.append(cells)
        }
    }
}

override funclayoutAttributesForElementsInRect(rect:CGRect)->[UICollectionViewLayoutAttributes]?{
    var layoutAttributes = UICollectionViewLayoutAttributes()

    iflet collectionView =self.collectionView {
        for in(0..<collectionView.numberOfSections()){

            let numberOfCellsInSection=collectionView.numberOfItemsInSection(i)

            for jin(0..<numberOfCellsInSection){
                let indexPath = NSIndexPath (forRow:j, inSection:i)
                iflet attributes=layoutAttributesForItemAtIndexPath(indexPath){
                    if(CGRectIntersectsRect(rect, attributes.frame)){
                        layoutAttributes.append(attributes)
                    }
                }
            }
        }
    }
    return layoutAttributes
}

override funclayoutAttributesForItemAtIndexPath(indexPath:NSIndexPath)->UICollectionViewLayoutAttributes?{
    let attributes=super.layoutAttributesForItemAtIndexPath(indexPath)
    attributes!.frame=sectionCells [indexPath.section] [indexPath.row]
    return attributes
}

override func collectionViewContentSize()->CGSize{
    return contentSize
}

CustomCollectionReusableView.swift

vartitle: UILabel!
varbackgroundImage:UIImageView!

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

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

    title=UILabel(frame:CGRectMake(frame.width*0.05,0, frame.width, frame.height))
    title.text="nil"
    title.textAlignment=NSTextAlignment.Left
    title.textColor=UIColor.redColor()

    let bgImage: UIImage=UIImage(named: "image02.jpg")!
    backgroundImage=UIImageView (frame: CGRectMake(0,0, frame.width, frame.height))
    backgroundImage.image=bgImage

    self.addSubview(backgroundImage)
    self.addSubview(title)

}

swift

2022-09-30 19:45

1 Answers

Have you read this document carefully?

Collection View Programming Guide

(HTML's English version may be easier to read than the Japanese version of pdf.)

here:

(From scenario in Table 3-1: I want to add a new auxiliary or decorative view)

The standard 」flow layout は class can only add section headers and section footers as auxiliary views and does not support decorative views.If you want to add an auxiliary or decorative view, you must override at least the following methods:

  • layoutAttributesForElementsInRect: (required)
  • layoutAttributesForItemAtIndexPath: (required)
  • layoutAttributesForSupplementaryViewOfKind:atIndexPath: (for auxiliary view)
  • layoutAttributesForDecorationViewOfKind:atIndexPath: (for Decorative View)

In your case, you are trying to define a header that is an auxiliary view, so you need to implement the following three methods correctly:

  • layoutAttributesForElementsInRect:
  • layoutAttributesForItemAtIndexPath:
  • layoutAttributesForSupplementaryViewOfKind:atIndexPath:

(*1) Above, override is required (you need to override), but in this case, since it is not new, the behavior defined in UICollectionViewFlowLayout may still work.

In your code, you notice a problem with the implementation of layoutAttributesForElementsInRect(_:).

The layoutAttributesForElementsInRect: method calls super to retrieve cell layout attributes and add attributes for new auxiliary or decorative views to place in the specified rectangle.Other methods have the ability to specify attributes as needed.

(The above text doesn't seem to be a good thing to look at, so I recommend you to read the original text.)

In your layoutAttributesForElementsInRect: method, you can call super to get the layout attributes for the cells and then the attributes for any new supplementary or determination views that are in the specified reorder. Together.

(Practical translation of the last sentence: Use other methods to provide attributes if necessary.)

The key to this topic is that layoutAttributesForElementsInRect(_:) also requires you to return the layout attributes for auxiliary and decorative views.

Your code layoutAttributesForElementsInRect(_:) only tries to return layout attributes for cells, so as a result collectionView(_:viewForSupplementaryElementOfKind:indexPath:) is not called either.

Try modifying the above methods, for example:

override funclayoutAttributesForElementsInRect(rect:CGRect)->[UICollectionViewLayoutAttributes]?{
    var layoutAttributes = UICollectionViewLayoutAttributes()
    
    iflet collectionView =self.collectionView {
        for in 0..<collectionView.numberOfSections(){
            
            let numberOfCellsInSection=collectionView.numberOfItemsInSection(i)
            
            for jin0..<numberOfCellsInSection{
                let indexPath = NSIndexPath (forRow:j, inSection:i)
                iflet attributes=layoutAttributesForItemAtIndexPath(indexPath){
                    if(CGRectIntersectsRect(rect, attributes.frame)){
                        layoutAttributes.append(attributes)
                    }
                }
                // You must also return layout attributes for auxiliary and decorative views
                // Header and footer for UICollectionViewFlowLayout (both auxiliary views)
                iflet attributes=layoutAttributesForSupplementaryViewOfKind (UICollectionElementKindSectionHeader, atIndexPath:indexPath) {
                    if CGRectIntersectsRect(rect, attributes.frame) {
                        layoutAttributes.append(attributes)
                    }
                }
                iflet attributes=layoutAttributesForSupplementaryViewOfKind (UICollectionElementKindSectionFooter, atIndexPath:indexPath) {
                    if CGRectIntersectsRect(rect, attributes.frame) {
                        layoutAttributes.append(attributes)
                    }
                }
            }
        }
    }
    return layoutAttributes
}

At the very least, you should be able to resolve the above function (collectionView(_:viewForSupplementaryElementOfKind:indexPath:)) that is no longer calledTry it.


2022-09-30 19:45

If you have any answers or tips


© 2024 OneMinuteCode. All rights reserved.