美文网首页Swift
Swift - UICollectionView 使用2

Swift - UICollectionView 使用2

作者: sky_storming | 来源:发表于2018-08-01 16:06 被阅读57次

    整理一下UICollectionView的使用实现项目中的某些效果。

    效果图如下:


    效果图.png

    实现以上效果,并且可以无限循环,或者不无限循环。
    除了以上效果外,还可以自由搭配其他效果,这里就不多说了,可以自己试试。


    我们先来了解一下居中显示核心方法,如下:

    // 通过indexPath将视图滑动到你指定的item,并且可以设置该item在屏幕中显示的位置(横向:左中右;竖向:上中下)
    open func scrollToItem(at indexPath: IndexPath, at scrollPosition: UICollectionViewScrollPosition, animated: Bool)
    

    下面让我们了解一下思路:

    1. 看到这种效果我们应该首先会想到使用UICollectionView,因为它可以自定义布局,横向滑动也比较方便设置等;
    2. 决定使用什么控件实现需求中的效果,我们要开始思考效果中我们都需要对哪些进行设置,也就是UICollectionViewFlowLayout相关属性的设置。从图中我们可以想到要设置itemSize、item之间的间距、item与屏幕之间的间距、横向华动等;
    3. 新建一个CarouselView和CarouselCollectionCell,根据需求初始化控件,这里只是一张本地图片;
    4. 准备的差不多了,就开始编写代码吧,将要用到的东西,定义并初始化实现等等;
    5. 边编写代码变运行看看效果,不至于编写完效果不对还不好找原因;
    6. 大体效果出来后,我们考虑一下如何让每个item都显示在中间,这里就用到了我们上面提到的方法了;
    7. 我们需要让它以page的方式滑动(这里的page和UICollectionView的isPagingEnabled属性是两回事),这里我们需要使用UIScrollViewDelegate中的两个方法,一个是开始拖拽的方法scrollViewWillBeginDragging,另一个是结束拖拽方法scrollViewDidEndDragging,通过这两个方法记录x坐标值,计算出我们要显示的item,并且居中显示。那可能有人会问,为什么不直接设置UICollectionView的isPagingEnabled属性。因为我们的item并不是占屏幕的全部宽度,也就是除了当前的item还要加上两侧的item的一部分才是isPagingEnabled的整个屏幕宽,可想而知滑动后的效果并不是我们想要的那种了,可以自己实验一把;
    8. 封装、优化代码以便外部调用少量代码实现该效果,或其他地方使用。



    开始上代码:(这里省略cell的实现,可根据各自需求实现)

    以下代码都是在 CarouselView 中
    定义协议留待内部调用、外部使用
    /**
     *  cell相关协议方法
     **/
    @objc protocol CarouselViewDelegate: NSObjectProtocol {
        func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
        
        @objc optional func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
        @objc optional func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets
        @objc optional func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
    }
    
    定义需要的变量,并做一些初始化
        /*
         *  MARK: - 定义变量
         */
        // 屏幕的宽高
        fileprivate let kScreenW = UIScreen.main.bounds.size.width
        fileprivate let kScreenH = UIScreen.main.bounds.size.height
        
        // 代理
        weak var delegate: CarouselViewDelegate?
    
        // 标识当前索引值,默认为 0
        fileprivate var currentIndex: Int = 0
        // 开始/结束拖拽时的x坐标值,默认为 0
        fileprivate var dragStartX: CGFloat = 0
        fileprivate var dragEndX: CGFloat = 0
        // 记录cell的总个数,默认为 0
        fileprivate var dataCount: Int = 0
        // 标识是否已经计算了 expandCellCount,默认为 false
        fileprivate var isCalculateExpandCellCount: Bool = false
        
        // 标识是哪个section下的功能,默认为第0个
        public var section: Int = 0
        // 是否以page为基础滑动(即滑动一屏),默认为 false
        public var isPagingEnabled: Bool = false
        // item距屏幕两侧的间距,默认为 15
        public var sectionMargin: CGFloat = 15 {
            didSet {
                carouselLayout.sectionInset = UIEdgeInsets(top: 0, left: sectionMargin, bottom: 0, right: sectionMargin)
            }
        }
        // item与item之间的间距,默认为 10
        public var itemSpacing: CGFloat = 10 {
            didSet {
                carouselLayout.minimumLineSpacing = itemSpacing
                carouselLayout.minimumInteritemSpacing = itemSpacing
            }
        }
        
        // 控件
        public lazy var carouselCollection: UICollectionView = {
            let collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: self.carouselLayout)
            collectionView.delegate = self
            collectionView.dataSource = self
            collectionView.showsHorizontalScrollIndicator = false
            collectionView.backgroundColor = UIColor.white
            return collectionView
        }()
        fileprivate lazy var carouselLayout: UICollectionViewFlowLayout = {
            let layout = UICollectionViewFlowLayout()
            layout.minimumLineSpacing = itemSpacing
            layout.minimumInteritemSpacing = itemSpacing
            layout.scrollDirection = .horizontal
            return layout
        }()
        
        // 数据源
        public var dataSource: [Any] = [] {
            didSet {
                // 计算cell的总数量
                self.dataCount = dataSource.count
                calculateTotalCell()
            }
        }
        // 若要循环滚动效果,则需更改cell的总数量
        public var expandCellCount: Int = 0 {
            didSet {
                calculateTotalCell()
            }
        }
        // 从第几个cell开始显示的位置
        public var startPosition: Int = 0 {
            didSet {
                if dataSource.count > 0 {
                    startPosition = dataSource.count * startPosition
                }
                initCellPosition()
            }
        }
        // item的宽高
        fileprivate var itemWidth: CGFloat {
            get {
                return (kScreenW-sectionMargin*2)
            }
        }
        fileprivate var itemHeight: CGFloat {
            get {
                return self.carouselCollection.frame.size.height - 1
            }
        }
    
    初始化UICollectionView和UICollectionViewFlowLayout,在init方法里边调用
    /**
     *  初始化
     **/
    extension CarouselView {
        /*
         *  MARK: - 初始化UI
         */
        fileprivate func setupUI() {
            // 设置 UICollectionView
            carouselCollection.frame = CGRect(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height)
            self.addSubview(carouselCollection)
        }
    }
    
    实现UICollectionView的协议方法
    /**
     *  UICollectionViewDelegate, UICollectionViewDataSource
     **/
    extension CarouselView: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
        func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
            return dataCount
        }
        
        func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
            
            // 获取外部数据
            if delegate != nil && ((delegate?.responds(to: #selector(CarouselViewDelegate.collectionView(_:cellForItemAt:)))) ?? false) {
                let cell = delegate?.collectionView(collectionView, cellForItemAt: indexPath)
                if let tempCell = cell {
                    return tempCell
                }
            }
            
            return collectionView.dequeueReusableCell(withReuseIdentifier: "other", for: indexPath)
        }
        
        func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
            // 返回点击事件
            if delegate != nil && ((delegate?.responds(to: #selector(CarouselViewDelegate.collectionView(_:didSelectItemAt:)))) ?? false) {
                delegate?.collectionView!(collectionView, didSelectItemAt: indexPath)
            }
        }
        
        func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    
            // 获取外部数据
            if delegate != nil && ((delegate?.responds(to: #selector(CarouselViewDelegate.collectionView(_:layout:sizeForItemAt:)))) ?? false) {
                let itemSize = delegate?.collectionView!(collectionView, layout: collectionViewLayout, sizeForItemAt: indexPath)
                if let tempItemSize = itemSize {
                    return tempItemSize
                }
            }
            
            return CGSize(width: itemWidth, height: itemHeight)
        }
    
        func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
            // 获取外部数据
            if delegate != nil && ((delegate?.responds(to: #selector(CarouselViewDelegate.collectionView(_:layout:insetForSectionAt:)))) ?? false) {
                let inset = delegate?.collectionView!(collectionView, layout: collectionViewLayout, insetForSectionAt: section)
                if let tempInset = inset {
                    return tempInset
                }
            }
            
            return UIEdgeInsets(top: 0, left: sectionMargin, bottom: 0, right: sectionMargin)
        }
    }
    
    实现协议方法,记录x坐标值,并设置item的滚动和显示位置
    /**
     *  UIScrollViewDelegate
     **/
    extension CarouselView: UIScrollViewDelegate {
        /*
         *  MARK: - 手指拖动开始
         */
        func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
            // 记录拖拽开始时的x坐标的
            self.dragStartX = scrollView.contentOffset.x
        }
        
        /*
         *  MARK: - 手指拖动结束
         */
        func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
            // 判断是否按page滑动
            if !isPagingEnabled {
                return
            }
            
            // 记录拖拽结束时的x坐标的
            self.dragEndX = scrollView.contentOffset.x
            // 主线程刷新UI
            DispatchQueue.main.async {
                self.fixCellToCenter()
            }
        }
    }
    
    自定义方法,也是重要的一部分,处理数据和cell的方法
    /**
     *  计算cell的位置
     **/
    extension CarouselView {
        
        /*
         *  MARK: - 计算显示cell的总数
         */
        fileprivate func calculateTotalCell() {
            // 判断是否有数据,有则进行计算
            if dataSource.count > 0 {
                // 要额外添加的cell数量大于0,且没有计算过dataCount属性值,且dataCount值等于元数据的个数
                if (self.expandCellCount > 0 && !isCalculateExpandCellCount && dataCount <= dataSource.count) {
                    // 计算cell的总数
                    self.dataCount = self.dataCount * self.expandCellCount
                    
                    // 更新标识
                    self.isCalculateExpandCellCount = true
                    
                    // 刷新
                    self.carouselCollection.reloadData()
                    
                    initCellPosition()
                    return
                }
            }
            
            self.isCalculateExpandCellCount = false
        }
        
        /*
         *  MARK: - 初始化cell的位置
         */
        public func initCellPosition() {
            // 设置显示的位置(数据大于1条时,初始滚动到中间位置)
            if dataSource.count <= 1 && startPosition <= 0 {
                return
            }
            
            // 若是循环滑动的话,则初始时先让cell滑动到某一位置
            if startPosition > 0 && startPosition < dataCount {
                let scrollToIndexPath = IndexPath(item: startPosition, section: section)
                currentIndex = startPosition
                self.carouselCollection.scrollToItem(at: scrollToIndexPath, at: UICollectionViewScrollPosition.centeredHorizontally, animated: false)
            }
        }
        
        /*
         *  MARK: - 滑动cell时,计算该显示的cell
         */
        fileprivate func fixCellToCenter() {
            // 最小滚动距离(用来确定滚动的距离,从而决定是否滚动到下一页/上一页)
            let dragMinimumDistance = kScreenW / 2.0 - calculateWidth(60.0)
            
            // 判断滚动的方向
            if dragStartX - dragEndX >= dragMinimumDistance {
                // 向右
                currentIndex = currentIndex - 1
            } else if dragEndX - dragStartX >= dragMinimumDistance {
                // 向左
                currentIndex = currentIndex + 1
            }
            
            let maximumIndex = carouselCollection.numberOfItems(inSection: section) - 1
            currentIndex = currentIndex <= 0 ? 0 : currentIndex
            currentIndex = currentIndex >= maximumIndex ? maximumIndex : currentIndex
            
            // 滚动到具体的item,并居中显示
            let indexPath = IndexPath(item: currentIndex, section: section)
            carouselCollection.scrollToItem(at: indexPath, at: UICollectionViewScrollPosition.centeredHorizontally, animated: true)
        }
    }
    
    宽高适配方法
    /**
     *  按比例计算宽高
     **/
    extension CarouselView {
        /*
         *  MARK: - 计算宽度
         *
         *  @param actualWidth: 实际的宽度
         *  return 返回计算的宽度
         */
        fileprivate func calculateWidth(_ actualWidth: CGFloat) -> CGFloat {
            return (actualWidth * kScreenW / 375.0)
        }
        
        /*
         *  MARK: - 计算高度
         *
         *  @param actualHeight: 实际的高度
         *  return 返回计算的高度
         */
        fileprivate func calculateHeight(_ actualHeight: CGFloat) -> CGFloat {
            return (actualHeight * kScreenH / 667.0)
        }
    }
    

    封装部分到此就结束了。



    下面我们看一下在ViewController里的使用:

    定义变量
      /*
       *  定义变量
       */
      fileprivate lazy var dataSource: [String] = [
            "1.jpg",
            "2.jpg",
            "3.jpg",
            "4.jpg",
            "5.jpg",
        ]
        fileprivate var carouselView: CarouselView?
        // item标识符
        public var carouselItemIdentifier: String = "JYCarouselItemIdentifier"
    
    初始化
    /**
     *  初始化
     **/
    extension ViewController {
        fileprivate func setupUI() {
            carouselView = CarouselView(frame: CGRect(x: 0, y: 100, width: UIScreen.main.bounds.size.width, height: (125 * UIScreen.main.bounds.size.height / 667.0)))
            carouselView?.delegate = self
            carouselView?.startPosition = 100
            carouselView?.dataSource = dataSource
            carouselView?.expandCellCount = 1000
            carouselView?.isPagingEnabled = true
            self.view.addSubview(carouselView!)
            carouselView?.carouselCollection.register(CarouselCollectionCell.classForCoder(), forCellWithReuseIdentifier: carouselItemIdentifier)
        }
    }
    
    实现协议方法,可选的方法可以不实现,将按默认处理
    /**
     *  CarouselViewDelegate
     **/
    extension ViewController: CarouselViewDelegate {
        func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: carouselItemIdentifier, for: indexPath) as? CarouselCollectionCell
            let currentItem = indexPath.item % dataSource.count // 通过余数,取出对应的数据
            cell?.setData(data: dataSource, currentIndex: currentItem)
            return cell!
        }
        
        func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
            let currentItem = indexPath.item % dataSource.count // 通过余数,取出对应的数据
            if currentItem < dataSource.count - 1 {
                return CGSize(width: (carouselView?.frame.size.width)!-30, height: (carouselView?.frame.size.height)!-1)
            } else {
                return CGSize(width: 200, height: (carouselView?.frame.size.height)!-1)
            }
        }
        
        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, didSelectItemAt indexPath: IndexPath) {
            print(indexPath.item % dataSource.count)
        }
    }
    

    到此就结束了,如有不妥的地方望指正。

    相关文章

      网友评论

        本文标题:Swift - UICollectionView 使用2

        本文链接:https://www.haomeiwen.com/subject/tcqmmftx.html