UICollectionView horizontal paging - can I use Flow Layout?

后端 未结 8 2083
佛祖请我去吃肉
佛祖请我去吃肉 2020-12-07 09:27

This is related to but distinct from To use Flow Layout, or to Customize?.

Here is an illustration of what I’m trying to do:

相关标签:
8条回答
  • 2020-12-07 09:28

    Can simply change Scroll Direction in UICollectionView.xib to Horizontal. And use with the correct order of elements in the array.

    0 讨论(0)
  • 2020-12-07 09:39

    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 dynamically
    private var cellHeight = 90
    
    public 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))
    }
    

    }

    0 讨论(0)
  • 2020-12-07 09:40

    The previous above implementation was not complete, buggy, and with fixed cell size. Here's a more literal translation for the code:

    import UIKit
    
    class HorizontalFlowLayout: UICollectionViewLayout {
        var itemSize = CGSizeZero {
            didSet {
                invalidateLayout()
            }
        }
        private var cellCount = 0
        private var boundsSize = CGSizeZero
    
        override func prepareLayout() {
            cellCount = self.collectionView!.numberOfItemsInSection(0)
            boundsSize = self.collectionView!.bounds.size
        }
    
        override func collectionViewContentSize() -> CGSize {
            let verticalItemsCount = Int(floor(boundsSize.height / itemSize.height))
            let horizontalItemsCount = Int(floor(boundsSize.width / itemSize.width))
    
            let itemsPerPage = verticalItemsCount * horizontalItemsCount
            let numberOfItems = cellCount
            let numberOfPages = Int(ceil(Double(numberOfItems) / Double(itemsPerPage)))
    
            var size = boundsSize
            size.width = CGFloat(numberOfPages) * boundsSize.width
            return size
        }
    
        override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
            var allAttributes = [UICollectionViewLayoutAttributes]()
            for var i = 0; i < cellCount; i++ {
                let indexPath = NSIndexPath(forRow: i, inSection: 0)
                let attr = self.computeLayoutAttributesForCellAtIndexPath(indexPath)
                allAttributes.append(attr)
            }
            return allAttributes
        }
    
        override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
            return self.computeLayoutAttributesForCellAtIndexPath(indexPath)
        }
    
        override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
            return true
        }
    
        func computeLayoutAttributesForCellAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes {
            let row = indexPath.row
            let bounds = self.collectionView!.bounds
    
            let verticalItemsCount = Int(floor(boundsSize.height / itemSize.height))
            let horizontalItemsCount = Int(floor(boundsSize.width / itemSize.width))
            let itemsPerPage = verticalItemsCount * horizontalItemsCount
    
            let columnPosition = row % horizontalItemsCount
            let rowPosition = (row/horizontalItemsCount)%verticalItemsCount
            let itemPage = Int(floor(Double(row)/Double(itemsPerPage)))
    
            let attr = UICollectionViewLayoutAttributes(forCellWithIndexPath: indexPath)
    
            var frame = CGRectZero
            frame.origin.x = CGFloat(itemPage) * bounds.size.width + CGFloat(columnPosition) * itemSize.width
            frame.origin.y = CGFloat(rowPosition) * itemSize.height
            frame.size = itemSize
            attr.frame = frame
    
            return attr
        }
    }
    
    0 讨论(0)
  • 2020-12-07 09:44

    Your last resort, of course, would be to use multiple vertically collection views inside each section in an outer horizontally scrolling collection view. Apart from increasing code complexity and difficulty in performing inter-section cell animations, I can't think of major issues with this approach right off my head.

    0 讨论(0)
  • 2020-12-07 09:48
    @interface HorizontalCollectionViewLayout : UICollectionViewFlowLayout
    
    @end
    
    @implementation HorizontalCollectionViewLayout
    
    - (instancetype)init
    {
        self = [super init];
        if (self) {
            self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
            self.minimumLineSpacing = 0;
            self.minimumInteritemSpacing = 0;
        }
        return self;
    }
    
    - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
    {
        NSArray *attributesArray = [super layoutAttributesForElementsInRect:rect];
    
        NSInteger verticalItemsCount = (NSInteger)floorf(self.collectionView.bounds.size.height / self.itemSize.height);
        NSInteger horizontalItemsCount = (NSInteger)floorf(self.collectionView.bounds.size.width / self.itemSize.width);
        NSInteger itemsPerPage = verticalItemsCount * horizontalItemsCount;
    
        for (NSInteger i = 0; i < attributesArray.count; i++) {
            UICollectionViewLayoutAttributes *attributes = attributesArray[i];
            NSInteger currentPage = (NSInteger)floor((double)i / (double)itemsPerPage);
            NSInteger currentRow = (NSInteger)floor((double)(i - currentPage * itemsPerPage) / (double)horizontalItemsCount);
            NSInteger currentColumn = i % horizontalItemsCount;
            CGRect frame = attributes.frame;
            frame.origin.x = self.itemSize.width * currentColumn + currentPage * self.collectionView.bounds.size.width;
            frame.origin.y = self.itemSize.height * currentRow;
            attributes.frame = frame;
        }
        return attributesArray;
    }
    
    - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
    {
        return YES;
    }
    
    @end
    
    0 讨论(0)
  • 2020-12-07 09:50

    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.

    0 讨论(0)
提交回复
热议问题