UICollectionView horizontal paging - can I use Flow Layout? UICollectionView horizontal paging - can I use Flow Layout? ios ios

UICollectionView horizontal paging - can I use Flow Layout?


Here I share my simple implementation!

The .h file:

/**  * CollectionViewLayout for an horizontal flow type: * *  |   0   1   |   6   7   | *  |   2   3   |   8   9   |   ----> etc... *  |   4   5   |   10  11  | * * Only supports 1 section and no headers, footers or decorator views. */@interface HorizontalCollectionViewLayout : UICollectionViewLayout@property (nonatomic, assign) CGSize itemSize;@end

The .m file:

@implementation HorizontalCollectionViewLayout{    NSInteger _cellCount;    CGSize _boundsSize;}- (void)prepareLayout{    // Get the number of cells and the bounds size    _cellCount = [self.collectionView numberOfItemsInSection:0];    _boundsSize = self.collectionView.bounds.size;}- (CGSize)collectionViewContentSize{    // We should return the content size. Lets do some math:    NSInteger verticalItemsCount = (NSInteger)floorf(_boundsSize.height / _itemSize.height);    NSInteger horizontalItemsCount = (NSInteger)floorf(_boundsSize.width / _itemSize.width);    NSInteger itemsPerPage = verticalItemsCount * horizontalItemsCount;    NSInteger numberOfItems = _cellCount;    NSInteger numberOfPages = (NSInteger)ceilf((CGFloat)numberOfItems / (CGFloat)itemsPerPage);    CGSize size = _boundsSize;    size.width = numberOfPages * _boundsSize.width;    return size;}- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{    // This method requires to return the attributes of those cells that intsersect with the given rect.    // In this implementation we just return all the attributes.    // In a better implementation we could compute only those attributes that intersect with the given rect.    NSMutableArray *allAttributes = [NSMutableArray arrayWithCapacity:_cellCount];    for (NSUInteger i=0; i<_cellCount; ++i)    {        NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];        UICollectionViewLayoutAttributes *attr = [self _layoutForAttributesForCellAtIndexPath:indexPath];        [allAttributes addObject:attr];    }    return allAttributes;}- (UICollectionViewLayoutAttributes*)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{    return [self _layoutForAttributesForCellAtIndexPath:indexPath];}- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{    // We should do some math here, but we are lazy.    return YES;}- (UICollectionViewLayoutAttributes*)_layoutForAttributesForCellAtIndexPath:(NSIndexPath*)indexPath{    // Here we have the magic of the layout.    NSInteger row = indexPath.row;    CGRect bounds = self.collectionView.bounds;    CGSize itemSize = self.itemSize;    // Get some info:    NSInteger verticalItemsCount = (NSInteger)floorf(bounds.size.height / itemSize.height);    NSInteger horizontalItemsCount = (NSInteger)floorf(bounds.size.width / itemSize.width);    NSInteger itemsPerPage = verticalItemsCount * horizontalItemsCount;    // Compute the column & row position, as well as the page of the cell.    NSInteger columnPosition = row%horizontalItemsCount;    NSInteger rowPosition = (row/horizontalItemsCount)%verticalItemsCount;    NSInteger itemPage = floorf(row/itemsPerPage);    // Creating an empty attribute    UICollectionViewLayoutAttributes *attr = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];    CGRect frame = CGRectZero;    // And finally, we assign the positions of the cells    frame.origin.x = itemPage * bounds.size.width + columnPosition * itemSize.width;    frame.origin.y = rowPosition * itemSize.height;    frame.size = _itemSize;    attr.frame = frame;    return attr;}#pragma mark Properties- (void)setItemSize:(CGSize)itemSize{    _itemSize = itemSize;    [self invalidateLayout];}@end

And finally, if you want a paginated behaviour, you just need to configure your UICollectionView:

_collectionView.pagingEnabled = YES;

Hoping to be useful enough.


Converted vilanovi code to Swift in case someone, needs it in the future.

public class HorizontalCollectionViewLayout : UICollectionViewLayout {private var cellWidth = 90 // Don't kow how to get cell size dynamicallyprivate var cellHeight = 90public override func prepareLayout() {}public override func collectionViewContentSize() -> CGSize {    let numberOfPages = Int(ceilf(Float(cellCount) / Float(cellsPerPage)))    let width = numberOfPages * Int(boundsWidth)    return CGSize(width: CGFloat(width), height: boundsHeight)}public override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]? {    var allAttributes = [UICollectionViewLayoutAttributes]()    for (var i = 0; i < cellCount; ++i) {        let indexPath = NSIndexPath(forRow: i, inSection: 0)        let attr = createLayoutAttributesForCellAtIndexPath(indexPath)        allAttributes.append(attr)    }    return allAttributes}public override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes! {    return createLayoutAttributesForCellAtIndexPath(indexPath)}public override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {    return true}private func createLayoutAttributesForCellAtIndexPath(indexPath:NSIndexPath)    -> UICollectionViewLayoutAttributes {        let layoutAttributes = UICollectionViewLayoutAttributes(forCellWithIndexPath: indexPath)        layoutAttributes.frame = createCellAttributeFrame(indexPath.row)        return layoutAttributes}private var boundsWidth:CGFloat {    return self.collectionView!.bounds.size.width}private var boundsHeight:CGFloat {    return self.collectionView!.bounds.size.height}private var cellCount:Int {    return self.collectionView!.numberOfItemsInSection(0)}private var verticalCellCount:Int {    return Int(floorf(Float(boundsHeight) / Float(cellHeight)))}private var horizontalCellCount:Int {    return Int(floorf(Float(boundsWidth) / Float(cellWidth)))}private var cellsPerPage:Int {    return verticalCellCount * horizontalCellCount}private func createCellAttributeFrame(row:Int) -> CGRect {    let frameSize = CGSize(width:cellWidth, height: cellHeight )    let frameX = calculateCellFrameHorizontalPosition(row)    let frameY = calculateCellFrameVerticalPosition(row)    return CGRectMake(frameX, frameY, frameSize.width, frameSize.height)}private func calculateCellFrameHorizontalPosition(row:Int) -> CGFloat {    let columnPosition = row % horizontalCellCount    let cellPage = Int(floorf(Float(row) / Float(cellsPerPage)))    return CGFloat(cellPage * Int(boundsWidth) + columnPosition * Int(cellWidth))}private func calculateCellFrameVerticalPosition(row:Int) -> CGFloat {    let rowPosition = (row / horizontalCellCount) % verticalCellCount    return CGFloat(rowPosition * Int(cellHeight))}

}


You're right – that's not how a stock horizontally-scrolling collection view lays out cells. I'm afraid that you're going to have to implement your own custom UICollectionViewLayout subclass. Either that, or separate your models into sections.