I have the following UICollectionView
:
It has vertical
scrolling, 1 section
and 31 items
.
It has the basic setup and I am calculating itemSize
to fit exactly 7 per row
.
Currently it looks like this:
However, I would like to make an inset before first item, so that the layout is even and there are the same number of items in first and last row. This is static and will always contain 31 items, so I am basically trying to add left space/inset before first item, so that it looks like this:
I have tried using a custom UICollectionViewDelegateFlowLayout
method:
collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int)
But since there is only 1 section with 31 rows, it insets all of the rows, not just the first. I know I could probably add two more "blank" items, but I feel like there is a better solution I may not be aware of. Any ideas?
EDIT: I've tried Tarun's answer, but this doesn't work. Origin of first item changes, but the rest stays as is, therefore first overlaps the second and the rest remain as they were. Shifting them all doesn't work either. I ended up with:
Following Taran's suggestion, I've decided to use a custom UICollectionViewFlowLayout. Here is a generic answer that works for any number of items in the collectionView, as well as any inset value:
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let collectionView = self.collectionView else { return nil }
guard let attributes = super.layoutAttributesForElements(in: rect) else { return nil }
var newAttributes: [UICollectionViewLayoutAttributes] = []
for attribute in attributes {
if let cellAttribute = collectionView.collectionViewLayout.layoutAttributesForItem(at: attribute.indexPath) {
let itemSize = cellAttribute.frame.size.width + self.minimumInteritemSpacing
let targetOriginX = cellAttribute.frame.origin.x + CGFloat(self.itemInset) * itemSize
if targetOriginX <= collectionView.bounds.size.width {
cellAttribute.frame.origin.x = targetOriginX
} else {
let shiftedPosition = lround(Double((targetOriginX / itemSize).truncatingRemainder(dividingBy: CGFloat(self.numberOfColumns))))
cellAttribute.frame.origin.x = itemSize * CGFloat(shiftedPosition)
cellAttribute.frame.origin.y += itemSize
}
newAttributes.append(cellAttribute)
}
}
return newAttributes
}
where:
self.itemInset
is the value we want to inset from the left (2 for my initial question, but it can be any number from 0
to the number of columns-1
)self.numberOfColumns
is - as the name suggests - number of columns in the collectionView. This pertains to the number of days in my example and would always be equal to 7
, but one might want this to be a generic value for some other use case.Just for the sake of the completeness, I provide a method that calculates a size for my callendar collection view, based on the number of columns (days):
private func collectionViewItemSize() -> CGSize {
let dimension = self.collectionView.frame.size.width / CGFloat(Constants.numberOfDaysInWeek) - Constants.minimumInteritemSpacing
return CGSize(width: dimension, height: dimension)
}
For me, Constants.numberOfDaysInWeek
is naturally 7
, and Constants.minimumInteritemSpacing
is equal to 2
, but those can be any numbers you desire.