ios swift UICollectionView 分页 自定义layout 不用 PagingEnable

515 阅读2分钟
  • 当 分页width = frame.size.width 是 可以用isPagingEnabled

collectionView.isPagingEnabled = true
  • 当 分页width < frame.size.width 时 需要自定义 layout

  • 查了很多,都有一个问题,第一个item 和 最后一个item 都无法居中。如下面的图片

解决此问题的思路是: 设置 collectionView.contentInset 可以是第一个item 和最后一个item 都居中显示。

self.collectionView?.contentInset = UIEdgeInsets(top: top, left: 0, bottom: bottom, right: 0)

解决后的效果如下:

注意:为了让分页滑动效果和原生分页效果一样,我们需要设置collectionview的decelerationRate 属性 为.fast,否则,collectionview 在滑动的时候特别缓慢。

self.collectionView?.decelerationRate = .fast
  • 代码面前,文字是苍白的,layout 代码如下
//

//  LZCollectionPageLayout.swift

//  荔枝

//  Created by LiZhi on 2023/9/4.

//

import UIKit


protocol LZCollectionPageLayoutDelegate:AnyObject {

    func collectionPagerLayout(_ layout:BBCollectionPageLayout,scrollEndTo index:Int)

}



///UICollectionView 分页 Layout

class LZCollectionPageLayout: UICollectionViewFlowLayout {

    /// 上方偏移量

    public var marginInset =  0.0

    /// 缩放因子,0 不缩放,值越大,缩放越大

    private var viewHeight = 0.0

    private var itemHeight = 0.0

   open weak var delegate :PPCollectionPageLayoutDelegate?

    /// 布局之前的准备工作

    override func prepare() {

        super.prepare()
        
        self.collectionView?.decelerationRate = .fast

        if(self.scrollDirection == .vertical){

            self.viewHeight = CGRectGetHeight(self.collectionView?.frame ?? .zero)

            self.itemHeight = self.itemSize.height

            // 设置第一个和最后一个默认居中显示 中间其实只留了一个item的区域。

            let top = (viewHeight - itemHeight )/2 - marginInset

            let bottom = (viewHeight - itemHeight )/2 + marginInset

            self.collectionView?.contentInset = UIEdgeInsets(top: top, left: 0, bottom: bottom, right: 0)

        }else {

            self.viewHeight = CGRectGetWidth(self.collectionView?.frame ?? .zero)

            self.itemHeight = self.itemSize.width

            self.collectionView?.contentInset = UIEdgeInsets(top: 0, left: (viewHeight - itemHeight )/2, bottom: 0, right: (viewHeight - itemHeight )/2)

        }

        

    }

  


    ///    当bounds发生变化的时候是否应该重新进行布局

    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {

        return true

    }



    /// 滚动停下的偏移量 返回一个新的偏移点坐标(UICollectionView不通过设置 isPagingEnabled 来实现分页效果,

    /// - Parameters:

    ///   - proposedContentOffset: 计划将要停止的点

    ///   - velocity: 滚动速度

    /// - Returns: 滚动停止的点

    override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {

        var offset = proposedContentOffset

        let offsetY = self.scrollDirection == .vertical ? offset.y : offset.x

        let origin = self.scrollDirection == .vertical ? CGPoint(x: 0, y: offsetY) : CGPoint(x: offsetY, y: 0)

//        获取UICollectionView停止的时候的可视范围

        let rect = CGRect(origin: origin, size: (collectionView?.frame.size ?? CGSize.zero))

        
        // 取出计算好的布局属性

        let currentRects = self.layoutAttributesForElements(in: rect)

        
        // 计算collectionView的centerY值

        let centerY = self.scrollDirection == .vertical ? ( offset.y + (collectionView?.frame.size.height ?? 0) * 0.5) : ( offset.x + (collectionView?.frame.size.width ?? 0) * 0.5)


        // 中心距离 最近item 的差值

        var minDelta = MAXFLOAT


        // 修改布局属性 计算在可视范围的距离中心线最近的Item

        if let rects = currentRects {

            for currentLayoutAttributes in rects {

                let currentItemCenterY = self.scrollDirection == .vertical ? currentLayoutAttributes.center.y :  currentLayoutAttributes.center.x

                

                if fabsf(minDelta) > fabsf(Float(currentItemCenterY - centerY)) {

                    minDelta = Float(currentItemCenterY - centerY)

                }

            }

        }

        

        if self.scrollDirection == .vertical {

            offset.y += CGFloat(minDelta + Float(self.marginInset) )

        }else {

            offset.x += CGFloat(minDelta + Float(self.marginInset))

        }

       
        // 四舍五入取整

        let offsetIndex = Int( round((offsetY + (viewHeight - itemHeight )/2 ) / itemHeight ))

        self.delegate?.collectionPagerLayout(self, scrollEndTo: offsetIndex)

        return offset

    }

}
  • 国际惯例,附上demo

Demo 地址 github.com/lizhi0123/L…

  • 其它方法

网上查到的demo:github.com/labmain/UIC…

文章:labmain.com/2017/02/12/…

注意点:在uicollectionview 外部设置了 contentInset ,才会 第一条,最后一条item 居中显示

image.png

参考 github.com/panghaijiao… www.jianshu.com/p/ad8add005…