CollectionViewSection Inset Does Not Consider Header/Footer Views - ios

I have a collectionView with headers and footers. I want to add spacing at the end of the section.
I am using:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 0, left: 0, bottom: 16, right: 0)
}
However, this does not consider the footer view. therefore, the inset is added to the last index of that section. Is there any method or way to include the section footer before it applies the inset?
I need the inset under the footer view.
Illustration of existing functionality. (Yellow is the indexPath.item) the expected functionality is to have the inset (red) under the footer.

As you rightly pointed out, UICollectionView's section is located between header and footer.
If you want to add spacing below the footer, the best way would be to create custom footer view.
optional func collectionView(_ collectionView: UICollectionView,
viewForSupplementaryElementOfKind kind: String,
at indexPath: IndexPath) -> UICollectionReusableView
Here are the docs for this method.
Ideally, you would want to create a subclass for your custom Footer:]
class CustomFooterView: UICollectionReusableView {
}
Then, register it in viewDidLoad:
collectionView.register(CustomFooterView.self, forSupplementaryViewOfKind: .elementKindSectionFooter, withReuseIdentifier: "Your footer reuse ID")
Then, you could return it as so:
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
if kind == UICollectionView.elementKindSectionFooter {
return collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "Your footer reuse ID", for: indexPath)
}
else {
}
}
Now you just need to ensure that auto layout is able to determine the size of your footer. So, add a label to your custom footer and ensure that its bottom constraint is equal to your desired padding.
Note: there is one caveat. Now that you implement the method which returns your custom footer, and the same method is called for header, you would probably need to use a custom header as well. Or, you would need to register the standard UICollectionReusableView and deque it for your header.

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 .

Swift 4 - Collection View Apply Section insets to header?

I am using a Collection View and I am able to add Section insets to the cells inside the collection view, but it appears not to be applying to the Collection Views reusable view, is there away to set section insets for reusable views?
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
if(kind == UICollectionView.elementKindSectionFooter)
{
let footerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "ReviewsFooter", for: indexPath) as! ReviewFooterCell
footerView.reviewFooterDelegate = self
return footerView
}
else if(kind == UICollectionView.elementKindSectionHeader)
{
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "ReviewHeader", for: indexPath) as! ReviewHeaderCell
headerView.totalReviews.text = "Total Reviews: " + String(self.reviews.count)
return headerView
}
fatalError()
}
First of all, let's be clear on what exactly is Section Insets.
Section insets are margins applied only to the items in the section.
They represent the distance between the header view and the first line
of items and between the last line of items and the footer view. They
also indicate the spacing on either side of a single line of items.
They do not affect the size of the headers or footers themselves.
Here is how you can add insets of a section in collectionView,
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
}

UICollectionView dynamic header size calculation

I know there are already a lot of posts around this topic, but they somehow didn't lead me to a solution and at this point, after trying for days I don't know what to do.
So I have a UICollectionView where I have a header. For the header I created my own UICollectionReusableView. It contains a StackView with two labels in it. Both of them have dynamic sizes (Lines = 0). These are the the constraints for the StackView (I also tried setting the bottom constraint to = 0):
StackView constraints
I calculate the header size like this...
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
let item = displayItems.object(at: UInt(section)) as! MySectionDisplayItem
let reusableView: MyReusableView = UIView.fromNib()
reusableView.setTitle(text: item.getTitle())
.setSubtitle(subtitle: item.getSubtitle())
return reusableView.systemLayoutSizeFitting(UILayoutFittingCompressedSize)
}
...and return the view to be displayed like this:
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let item = displayItems.object(at: UInt(indexPath.section)) as! MySectionDisplayItem
let reusableView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "myReusableView", for: indexPath) as! MyReusableView
reusableView.setTitle(text: item.getTitle())
.setSubtitle(subtitle: item.getSubtitle())
return reusableView
}
In my UICollectionReusableView and UICollectionView Auto Layout is enabled.
Some things I tried:
set preferredMaxLayoutWidth to different positive values on my labels
embed the labels in separate views
work with constraints instead of using a StackView
I hope I didn't mix up some of the solutions I found, but anyways I don't think such a "simple" thing can be that hard to realize.
I finally solved the problem by constraining the width:
reusableView.translatesAutoresizingMaskIntoConstraints = true
reusableView.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width).isActive = true
Might be that you also have to play around with content compression resistances.

UICollectionView items order not reversed in right to left languages

I noticed a big issue where in right to left languages, the cells order is not properly reversed, only the alignment is correct. But only for horizontal flow layout, and if the collection view contain different cell sizes! Yes, I know this sound insane. If all the cells are the same size, the ordering and alignment is good!
Here is what I got so far with a sample app (isolated to make sure this is the actual issue, and not something else):
(First bar should always be drawn blue, then size increase with index.)
Is this an internal bug in UICollectionViewFlowLayout (on iOS 11)? Or is there something obvious that I am missing?
Here is my test code (Nothing fancy + XIB with UICollectionView):
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 6
}
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "test", for: indexPath)
cell.backgroundColor = (indexPath.item == 0) ? UIColor.blue : UIColor.red
return cell
}
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: (10 * indexPath.item) + 20, height: 170)
}
Automatic right-to-left support on dynamically-sized UICollectionViews is not a supported configuration. For this to work, you need to explicitly sign up for automatic layout mirroring as follows:
Create a subclass of UICollectionViewFlowLayout
Override flipsHorizontallyInOppositeLayoutDirection, and return true in Swift or YES in Objective-C
Set that as the layout of your collection view
This property is defined on UICollectionViewLayout (parent of Flow), so you can technically use this property on any custom layout you already have.
I believe that for this you will have to implement your own custom collectionViewLayout - although I understand that one would expect that it would automatically work just as the right-to-left on the rest of the components.

UICollectionView: dynamic cell heights with UIStackView

I have a storyboard, consisting of a single UICollectionView with multiple cells, each of varying height. The first cell takes the height from the UICollectionViewDelegateFlowLayout:
func collectionView(collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
.. but I'd like the second cell to be shorter.
I've placed two UIStackViews inside a "master" UIStackView inside the cell, and each of the inner UIStackViews has one or more labels, like this:
cell
--> stackView (master)
--> stackView (1)
--> label
--> stackView (2)
--> label
--> label (etc)
.. in the hope that the UIStackView would make the cell height dynamic, but it doesn't. It takes the height from the UICollectionViewDelegateFlowLayout as before.
How should I be doing this?
You need to compute the size of the content for the CollectionViewCell and return it to the sizeForItemAt function.
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
// Create an instance of the `FooCollectionViewCell`, either from nib file or from code.
// Here we assume `FooCollectionViewCell` is created from a FooCollectionViewCell.xib
let cell: FooCollectionViewCell = UINib(nibName: "FooCollectionViewCell", bundle: nil)
.instantiate(withOwner: nil, options: nil)
.first as! FooCollectionViewCell
// Configure the data for your `FooCollectionViewCell`
cell.stackView.addArrangedSubview(/*view1*/)
cell.stackView.addArrangedSubview(/*view2*/)
// Layout the collection view cell
cell.setNeedsLayout()
cell.layoutSubviews()
// Calculate the height of the collection view based on the content
let size = cell.contentView.systemLayoutSizeFitting(
CGSize(width: collectionView.bounds.width, height: 0),
withHorizontalFittingPriority: UILayoutPriorityRequired,
verticalFittingPriority: UILayoutPriorityFittingSizeLevel)
return size
}
With this, you will have a dynamic cell heights UICollectionView.
Further notes:
for configuration of the collection view cell, you can create a helper function func configure(someData: SomeData) on FooCollectionViewCell so that the code could be shared between the cellForItemAt function and sizeForItemAt function.
// Configure the data for your `FooCollectionViewCell`
cell.stackView.addArrangedSubview(/*view1*/)
cell.stackView.addArrangedSubview(/*view2*/)
For these two lines of code, it seems only needed if the UICollectionViewCell contains vertical UIStackView as subViews (likely a bug from Apple).
// Layout the collection view cell
cell.setNeedsLayout()
cell.layoutSubviews()
If you'd like to change the height of your cells you're going to have to change the height you return in the sizeForItemAtIndexPath. The stack views aren't going to have any effect here. Here's an example of what you can do:
func collectionView(collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
if indexPath.row == 1 {
return CGSizeMake(width, height/2)
}
return CGSizeMake(width, height)
}
This will change the size of your cells at row 1. You can also use indexPath.section to choose sections. Hope this helps.

Resources