UICollectionView horizontal paging - can I use Flow Layout?

后端 未结 8 2084
佛祖请我去吃肉
佛祖请我去吃肉 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:51

    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.

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

    Swift 4

    Code:

    public class HorizontalFlowLayout: UICollectionViewLayout {
        var itemSize = CGSize(width: 0, height: 0) {
            didSet {
                invalidateLayout()
            }
        }
        private var cellCount = 0
        private var boundsSize = CGSize(width: 0, height: 0)
    
        public override func prepare() {
            cellCount = self.collectionView!.numberOfItems(inSection: 0)
            boundsSize = self.collectionView!.bounds.size
        }
        public override var 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
        }
    
        public override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
            var allAttributes = [UICollectionViewLayoutAttributes]()
            for i in 0...(cellCount-1) {
                let indexPath = IndexPath(row: i, section: 0)
                let attr = self.computeLayoutAttributesForCellAt(indexPath: indexPath)
                allAttributes.append(attr)
            }
            return allAttributes
        }
    
        public override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
            return computeLayoutAttributesForCellAt(indexPath: indexPath)
        }
    
        public override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
            return true
        }
    
        private func computeLayoutAttributesForCellAt(indexPath:IndexPath)
            -> 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(forCellWith: indexPath)
    
                var frame = CGRect(x: 0, y: 0, width: 0, height: 0)
                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
        }
    }
    

    This is the Swift 3 version of @GuilhermeSprint answer

    Code:

    public class HorizontalCollectionViewLayout : UICollectionViewLayout {
        var itemSize = CGSize(width: 0, height: 0) {
            didSet {
                invalidateLayout()
            }
        }
        private var cellCount = 0
        private var boundsSize = CGSize(width: 0, height: 0)
    
        public override func prepare() {
            cellCount = self.collectionView!.numberOfItems(inSection: 0)
            boundsSize = self.collectionView!.bounds.size
        }
        public override var 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
        }
    
        public override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
            var allAttributes = [UICollectionViewLayoutAttributes]()
            for i in 0...(cellCount-1) {
                let indexPath = IndexPath(row: i, section: 0)
                let attr = self.computeLayoutAttributesForCellAt(indexPath: indexPath)
                allAttributes.append(attr)
            }
            return allAttributes
        }
    
        public override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
            return computeLayoutAttributesForCellAt(indexPath: indexPath)
        }
    
        public override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
            return true
        }
    
        private func computeLayoutAttributesForCellAt(indexPath:IndexPath)
            -> 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(forCellWith: indexPath)
    
                var frame = CGRectMake(0, 0, 0, 0)
                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
        }
    }
    

    Usage:

        // I want to have 4 items in the page / see screenshot below
        let itemWidth = collectionView.frame.width / 2.0
        let itemHeight = collectionView.frame.height / 2.0
        let horizontalCV = HorizontalCollectionViewLayout();
        horizontalCV.itemSize = CGSize(width: itemWidth, height: itemHeight)
        collectionView.collectionViewLayout = horizontalCV
    

    Result

    My Delegates extension if you wanna check it also

    extension MyViewController : UICollectionViewDelegateFlowLayout, UICollectionViewDataSource{
        // Delegate
        func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
            print("Clicked")
        }
    
        // DataSource
        func numberOfSections(in collectionView: UICollectionView) -> Int {
            return 1
        }
    
        func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
            if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: BottomMenuCCell.xib, for: indexPath) as? BottomMenuCCell {
                cell.ibi = bottomMenuButtons[indexPath.row]
                cell.layer.borderWidth = 0
                return cell
            }
            return BaseCollectionCell()
        }
        func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
            return CGSize.init(width: (collectionView.width / 2.0), height: collectionView.height / 2.0)
        }
        func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
            return bottomMenuButtons.count
        }
    
        // removing spacing between items
        func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
            return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
        }
        func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
            return 0.0
        }
        func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
            return 0.0
        }
    }
    
    0 讨论(0)
提交回复
热议问题