Dynamic CollectionViewHeader Cell Sizing of a complex layout - ios

I have read multiple answers on StackOverflow but nothing seemed to works or is used for my case.
I have a collectionview header that needs to be self sizing, but the header also contains views that can be hidden with a stackview or not depending on the data.
I don't know how I am suppose to get this to self sizing:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
if(section == 0){
return CGSize(width: view.frame.width, height: 500)
}
return CGSize(width: view.frame.width, height: 100)
}
My header contains multiple views that changes height dynamically because of the stackview.
Is there a way that I could layout the header then get the height of a view inside the cell then readjust cell size?

first of all your section header is just a UICollectionViewCell or UICollectionView.elementKindSectionHeader ?
second I think this might help I have a smiler case in my project and it worked for me
https://www.vadimbulavin.com/collection-view-cells-self-sizing/

Related

How to implement a horizontally-scrolling collection view with a header on top?

I want to have a collection view that scrolls horizontally, but I also want it to have a header on top of and to the right. I've tried implementing this as I would a vertically-scrolling collection view, and the header ends up to the left of the first cell.
Here is how I dequeue the header:
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let header = dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: identifier, for: indexPath) as! HeaderView
header.setWidth(to: headerWidth)
return header
}
Size of the header:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: collectionView.frame.width, height: 12)
}
Apart from that, the only other properties I initialize are the scroll direction and insets of the collection view
Headers positions : are in the Top of the section in case of scrolling vertically and in the left of the section in case of horizontally scrolling , So it's normal that it's showing in the left of the section in your case .
If you want to display your reusable view in the right of every section You can use UICollectionView.elementKindSectionFooter instead .
But if you want to display your reusable view between every section , You can set the size of header view in the very first section to .zero .
You can manipulate in every header view size with referenceSizeForHeaderInSection function like so:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
if section == 0 {
return .zero
}
return CGSize(width: collectionView.frame.width, height: 12)
}
In this case i removed (technically not removed but hide) the first header in the left , Which is in the first section , But you can set the size for every header .

Calculating cell width in UICollectionView inside ContainerView

I have a UICollectionView and I have implemented the delegate to calculate the width of my cells for 0 spacing between cells.
It works great on its own, but when I have it inside a container view less than the size of the device, iOS incorrectly works out the spacing between the cells adding a horizontal space I don't want.
I have verified the width I am using to calculate the cell size is correct, so I'm not sure what is causing the problem.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let widthPerItem = view.frame.width / itemsPerRow
return CGSize(width: widthPerItem, height: 60)
}
You are calculating your cell size based upon the size of the view. Since the collectionView doesn't take up the full width of the screen, you are getting the incorrect value. Instead, you should base your calculation on the width of the collectionView itself.
Use the bounds of the collectionView instead of the frame of the view:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let widthPerItem = collectionView.bounds.width / itemsPerRow
return CGSize(width: widthPerItem, height: 60)
}
Ok, the problem was actually related to my uicollectionviewcontroller not correctly sizing to it's parent container view. Changing to a uiviewcontroller with an embedded uicollectionview plus constraints fixed the problem.

iOS UICollectionView with self sizing items bug?

I'm trying to implement UICollectionView (flowLayout) with self sizing items.
Implementation is very simple - I have just set estimatedItemSize and set UICollectionViewCell constraints to manage it's size.
Everything works fine at first data reload after collectionView was created, but on another or some other reload few items at the top becomes same size as estimatedItemSize is. If scroll down and up - items size looks good again.
I have spend 2 days with this issue experimenting different cell constraints, trying to setNeedsLayout in various places and other stuff around collectionView. Is it bug?
In this post I had an interesting problem that I found my own answer to. There are two important functions to be using properly for the sizing purposes which are:
minimumInteritemSpacingForSectionAtIndex
insetForSectionAtIndex
sizeForItemAtIndexPath
Example of me using them in Swift
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionView, minimumInteritemSpacingForSectionAtIndex section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets {
return UIEdgeInsetsMake(0, 0, 0, 0)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize {
let screenRect: CGRect = collectionView.bounds
let screenWidth: CGFloat = screenRect.size.width
let cellWidth: CGFloat = screenWidth / 24
//Replace the divisor with the column count requirement. Make sure to have it in float.
let size: CGSize = CGSize(width: cellWidth, height: cellWidth)
return size
}
The biggest secret is to be careful about attaching your views to the trailing edge and the bottom layout. If you only attach them to leading and the top, then you can set the width and height programmatically through frame or constraints. I think that doing it through constraints is a little more straight forward, though in my personal project I have chose to do it the other way because it makes the functionality slightly cleaner in my opinion.
Try to accomplish as much of a cells layout as possible inside of cellForItemAt.

Getting height of collection view is wrong after auto layout have done its job

How do I get the height of the collection view in the delegate method so that the cells height are resized? The collection view is not fixed height in my layout so auto layout changes its height, but the cells height is not correct with the current code.
ThiscollectionView.bounds.size.height returns the wrong height it seems. It seems like it is the value before it was resized.
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
if indexPath.row % 4 == 0 {
return CGSize(width: 100, height: collectionView.bounds.size.height)
} else {
return CGSize(width: 100, height: collectionView.bounds.size.height)
}
}
You need to wait for ViewDidLayoutSubviews, to get actual size. The problem is -sizeForItemAtIndexPath get calling before that. It's not a great solution, but try to refresh CollectionView cells size after ViewDidLayoutSubviews.

Disable supplementary view

What is the correct of of disabling the supplementary views (e.g. header)? One time I want to show them (iPhone) and one time I don't want to show them (iPad).
The only idea I currently have is to return a size of zero in referenceSizeForHeaderInSection. But I think it's kind of an overhead to create views, which aren't used at all. On the other side I have to implement collectionView:viewForSupplementaryElementOfKind:atIndexPath: and I can't return nil, because the app crashes then.
How can I disable the supplementary views in UICollectionView?
If you return CGSizeZero from collectionView:layout:referenceSizeForHeaderInSection: delegate method collectionView:viewForSupplementaryElementOfKind:atIndexPath: won't be called.
Apple Developer API Reference states about collectionView(_:layout:referenceSizeForHeaderInSection:):
If the size in the appropriate scrolling dimension is 0, no header is added.
The following Swift 5 code snippet shows a possible implementation of collectionView(_:layout:referenceSizeForHeaderInSection:) in order to display or not UICollectionView supplementary views:
// MARK: - UICollectionViewDelegateFlowLayout
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
if UIDevice.current.userInterfaceIdiom == UIUserInterfaceIdiom.pad {
return CGSize.zero
} else {
return CGSize(width: 0, height: 40)
// or
//let flowLayout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
//return flowLayout.headerReferenceSize
}
}

Resources