I am currently using UICollectionView
for the user interface grid, and it works fine. However, I\'d like to be enable horizontal scrolling. The grid supports 8
Swift 2.0 Works fine for me!
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets {
let edgeInsets = (screenWight - (CGFloat(elements.count) * 50) - (CGFloat(elements.count) * 10)) / 2
return UIEdgeInsetsMake(0, edgeInsets, 0, 0);
}
Where: screenWight: basically its my collection's width (full screen width) - I made constants: let screenWight:CGFloat = UIScreen.mainScreen().bounds.width because self.view.bounds shows every-time 600 - coz of SizeClasses elements - array of cells 50 - my manual cell width 10 - my distance between cells
My solution for static sized collection view cells which need to have padding on left and right-
func collectionView(collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
insetForSectionAtIndex section: Int) -> UIEdgeInsets {
let flowLayout = (collectionViewLayout as! UICollectionViewFlowLayout)
let cellSpacing = flowLayout.minimumInteritemSpacing
let cellWidth = flowLayout.itemSize.width
let cellCount = CGFloat(collectionView.numberOfItemsInSection(section))
let collectionViewWidth = collectionView.bounds.size.width
let totalCellWidth = cellCount * cellWidth
let totalCellSpacing = cellSpacing * (cellCount - 1)
let totalCellsWidth = totalCellWidth + totalCellSpacing
let edgeInsets = (collectionViewWidth - totalCellsWidth) / 2.0
return edgeInsets > 0 ? UIEdgeInsetsMake(0, edgeInsets, 0, edgeInsets) : UIEdgeInsetsMake(0, cellSpacing, 0, cellSpacing)
}
Similar to other answers, this is a dynamic answer that should work for static sized cells. The one modification I made is that I put the padding on both sides. If I didn't do this, I experienced problems.
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewFlowLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
NSInteger numberOfItems = [collectionView numberOfItemsInSection:section];
CGFloat combinedItemWidth = (numberOfItems * collectionViewLayout.itemSize.width) + ((numberOfItems - 1) * collectionViewLayout.minimumInteritemSpacing);
CGFloat padding = (collectionView.frame.size.width - combinedItemWidth) / 2;
return UIEdgeInsetsMake(0, padding, 0, padding);
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout
let numberOfItems = CGFloat(collectionView.numberOfItems(inSection: section))
let combinedItemWidth = (numberOfItems * flowLayout.itemSize.width) + ((numberOfItems - 1) * flowLayout.minimumInteritemSpacing)
let padding = (collectionView.frame.width - combinedItemWidth) / 2
return UIEdgeInsets(top: 0, left: padding, bottom: 0, right: padding)
}
Also, if you are still are experiencing problems, make sure that you set both the minimumInteritemSpacing
and minimumLineSpacing
to the same values since these values seem to be interrelated.
UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout;
flowLayout.minimumInteritemSpacing = 20.0;
flowLayout.minimumLineSpacing = 20.0;
This is how i obtained a collection view that was center aligned with a fixed Interitem spacing
#define maxInteritemSpacing 6
#define minLineSpacing 3
@interface MyFlowLayout()<UICollectionViewDelegateFlowLayout>
@end
@implementation MyFlowLayout
- (instancetype)init
{
self = [super init];
if (self) {
self.minimumInteritemSpacing = 3;
self.minimumLineSpacing = minLineSpacing;
self.scrollDirection = UICollectionViewScrollDirectionVertical;
}
return self;
}
- (NSArray *) layoutAttributesForElementsInRect:(CGRect)rect {
NSArray *answer = [super layoutAttributesForElementsInRect:rect];
if (answer.count > 0) {
NSMutableArray<NSMutableArray<UICollectionViewLayoutAttributes *> *> *rows = [NSMutableArray array];
CGFloat maxY = -1.0f;
NSInteger currentRow = 0;
//add first item to row and set its maxY
[rows addObject:[@[answer[0]] mutableCopy]];
maxY = CGRectGetMaxY(((UICollectionViewLayoutAttributes *)answer[0]).frame);
for(int i = 1; i < [answer count]; ++i) {
//handle maximum spacing
UICollectionViewLayoutAttributes *currentLayoutAttributes = answer[i];
UICollectionViewLayoutAttributes *prevLayoutAttributes = answer[i - 1];
NSInteger maximumSpacing = maxInteritemSpacing;
NSInteger origin = CGRectGetMaxX(prevLayoutAttributes.frame);
if(origin + maximumSpacing + currentLayoutAttributes.frame.size.width < self.collectionViewContentSize.width) {
CGRect frame = currentLayoutAttributes.frame;
frame.origin.x = origin + maximumSpacing;
currentLayoutAttributes.frame = frame;
}
//handle center align
CGFloat currentY = currentLayoutAttributes.frame.origin.y;
if (currentY >= maxY) {
[self shiftRowToCenterForCurrentRow:rows[currentRow]];
//next row
[rows addObject:[@[currentLayoutAttributes] mutableCopy]];
currentRow++;
}
else {
//same row
[rows[currentRow] addObject:currentLayoutAttributes];
}
maxY = MAX(CGRectGetMaxY(currentLayoutAttributes.frame), maxY);
}
//mark sure to shift 1 row items
if (currentRow == 0) {
[self shiftRowToCenterForCurrentRow:rows[currentRow]];
}
}
return answer;
}
- (void)shiftRowToCenterForCurrentRow:(NSMutableArray<UICollectionViewLayoutAttributes *> *)currentRow
{
//shift row to center
CGFloat endingX = CGRectGetMaxX(currentRow.lastObject.frame);
CGFloat shiftX = (self.collectionViewContentSize.width - endingX) / 2.f;
for (UICollectionViewLayoutAttributes *attr in currentRow) {
CGRect shiftedFrame = attr.frame;
shiftedFrame.origin.x += shiftX;
attr.frame = shiftedFrame;
}
}
@end
Here is my solution with a few assumptions:
Feel free to adjust to meet your needs.
Centered layout with variable cell width:
protocol HACenteredLayoutDelegate: UICollectionViewDataSource {
func getCollectionView() -> UICollectionView
func sizeOfCell(at index: IndexPath) -> CGSize
func contentInsets() -> UIEdgeInsets
}
class HACenteredLayout: UICollectionViewFlowLayout {
weak var delegate: HACenteredLayoutDelegate?
private var cache = [UICollectionViewLayoutAttributes]()
private var contentSize = CGSize.zero
override var collectionViewContentSize: CGSize { return self.contentSize }
required init(delegate: HACenteredLayoutDelegate) {
self.delegate = delegate
super.init()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func invalidateLayout() {
cache.removeAll()
super.invalidateLayout()
}
override func prepare() {
if cache.isEmpty && self.delegate != nil && self.delegate!.collectionView(self.delegate!.getCollectionView(), numberOfItemsInSection: 0) > 0 {
let insets = self.delegate?.contentInsets() ?? UIEdgeInsets.zero
var rows: [(width: CGFloat, count: Int)] = [(0, 0)]
let viewWidth: CGFloat = UIScreen.main.bounds.width
var y = insets.top
var unmodifiedIndexes = [IndexPath]()
for itemNumber in 0 ..< self.delegate!.collectionView(self.delegate!.getCollectionView(), numberOfItemsInSection: 0) {
let indexPath = IndexPath(item: itemNumber, section: 0)
let cellSize = self.delegate!.sizeOfCell(at: indexPath)
let potentialRowWidth = rows.last!.width + (rows.last!.count > 0 ? self.minimumInteritemSpacing : 0) + cellSize.width + insets.right + insets.left
if potentialRowWidth > viewWidth {
let leftOverSpace = max((viewWidth - rows[rows.count - 1].width)/2, insets.left)
for i in unmodifiedIndexes {
self.cache[i.item].frame.origin.x += leftOverSpace
}
unmodifiedIndexes = []
rows.append((0, 0))
y += cellSize.height + self.minimumLineSpacing
}
unmodifiedIndexes.append(indexPath)
let attribute = UICollectionViewLayoutAttributes(forCellWith: indexPath)
rows[rows.count - 1].count += 1
rows[rows.count - 1].width += rows[rows.count - 1].count > 1 ? self.minimumInteritemSpacing : 0
attribute.frame = CGRect(x: rows[rows.count - 1].width, y: y, width: cellSize.width, height: cellSize.height)
rows[rows.count - 1].width += cellSize.width
cache.append(attribute)
}
let leftOverSpace = max((viewWidth - rows[rows.count - 1].width)/2, insets.left)
for i in unmodifiedIndexes {
self.cache[i.item].frame.origin.x += leftOverSpace
}
self.contentSize = CGSize(width: viewWidth, height: y + self.delegate!.sizeOfCell(at: IndexPath(item: 0, section: 0)).height + insets.bottom)
}
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var layoutAttributes = [UICollectionViewLayoutAttributes]()
for attributes in cache {
if attributes.frame.intersects(rect) {
layoutAttributes.append(attributes)
}
}
return layoutAttributes
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
if indexPath.item < self.cache.count {
return self.cache[indexPath.item]
}
return nil
}
}
Result:
It's easy to calculate insets dynamically, this code will always center your cells:
NSInteger const SMEPGiPadViewControllerCellWidth = 332;
...
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
{
NSInteger numberOfCells = self.view.frame.size.width / SMEPGiPadViewControllerCellWidth;
NSInteger edgeInsets = (self.view.frame.size.width - (numberOfCells * SMEPGiPadViewControllerCellWidth)) / (numberOfCells + 1);
return UIEdgeInsetsMake(0, edgeInsets, 0, edgeInsets);
}
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
[self.collectionView.collectionViewLayout invalidateLayout];
}