利用UICollectionView实现无限轮播(Swift)

2017-01-10  本文已影响634人  乂滥好人

1、主要使用UICollectionView实现无限轮播图,封装成一个控件(集成UIView),方便使用。

import UIKit
private let kCycleCellID = "kCycleCellID"
class RecommendCycleView: UIView {
    /** 数据模型(由外界传值) */
    var cycleModelArr:[CycleModel]? {
        didSet{
            // 1、刷新表格
            collectionView.reloadData()
            // 2、设置page的个数
            pageControl.numberOfPages = cycleModelArr?.count ?? 0
            // 3、默认滚动到中间某一个位置防止用户一开始就往前拉,看不到东西(有点问题,注释掉)
//            let indexPath = IndexPath(item: (cycleModelArr?.count ?? 0) * 10, section: 0)
//            collectionView.scrollToItem(at: indexPath, at: .left, animated: false)
            
            // 4.添加定时器(最好新移除在添加)
            removeCycleTimer()
            addCycleTimer()
        }
    }
    /** 定时器 */
    var cycleTime: Timer?
    @IBOutlet weak var collectionView: UICollectionView!
    @IBOutlet weak var pageControl: UIPageControl!
    
// MARK- 系统回调(从nib中)
    override func awakeFromNib() {
        super.awakeFromNib()
        // 0、设置该控件不随着父控件的拉伸而拉伸(重要)
        autoresizingMask = UIViewAutoresizing()
        // autoresizingMask = .None 3.0之前的写法
        // 1、注册cell
        collectionView.register(UINib(nibName: "CollectionViewCycleCell", bundle: nil), forCellWithReuseIdentifier: kCycleCellID)
    }
    
    // 当控件是从nib中获取的话,尺寸往往是不对的,最好在layoutSubviews中设置尺寸
    override func layoutSubviews() {
        super.layoutSubviews()
        // 设置collectionView的layou
        let layout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
        layout.itemSize = collectionView.bounds.size
    }
}

// MARK:- 提供一个快速创建View的类方法
extension RecommendCycleView {
    class func recommendCycleView() -> RecommendCycleView{
        return Bundle.main.loadNibNamed("RecommendCycleView", owner: nil, options: nil)?.first as! RecommendCycleView
    }
}

// MARK:- <UICollectionViewDataSource>
extension RecommendCycleView:UICollectionViewDataSource, UICollectionViewDelegate {
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        // 1、需要无限轮播,所以在返回的时候给当前属性添加更多item
        return (cycleModelArr?.count ?? 0) * 10000
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: kCycleCellID, for: indexPath) as! CollectionViewCycleCell
        // 2、但是如果一直在 cycleModelArr 中取,数组肯定会越界,所以要 % cycleModelArr!.count 防止越界问题
        cell.cycleModel = cycleModelArr![indexPath.item % cycleModelArr!.count]
        return cell
    }
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        print("当前点击了第 \(indexPath.item % cycleModelArr!.count) 个Item")
    }
    
    // 监听collectionView的滚到
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        // 1、获取滚动的偏移量 + scrollView.bounds.width * 0.5给偏移量加一半,当滑动一般就滚动pageControl的当前选中
        let offsetX = scrollView.contentOffset.x + scrollView.bounds.width * 0.5
        // 2、计算pageContra的currentIndex。这 % (cycleModelArr?.count ?? 1)也是跟上同样道理
        pageControl.currentPage = Int(offsetX / scrollView.bounds.width) % (cycleModelArr?.count ?? 1)
    }
    
    // 监听当手动拖拽的时候,移除定时器
    func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
        removeCycleTimer()
    }
    
    // 监听当手动拖拽结束时,添加定时器
    func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        addCycleTimer()
    }
}

extension RecommendCycleView{
    
    fileprivate func addCycleTimer() {
        cycleTime = Timer(timeInterval: 3.0, target: self, selector: #selector(self.scrollToNext), userInfo: nil, repeats: true)
        // 把定时器加入 运行循环中
        RunLoop.main.add(cycleTime!, forMode: RunLoopMode.commonModes)
    }
    
    /** 删除计时器 */
    fileprivate func removeCycleTimer() {
        cycleTime?.invalidate() // 从运行循环中移除
        cycleTime = nil
    }
    
    // 注意:当 extension 中要给方法加私有修饰词的话,前面必须加 @objc
    @objc fileprivate func scrollToNext() {
        // 1.获取collectionView的X轴滚动的偏移量
        let currentOffsetX = collectionView.contentOffset.x
        let offsetX = currentOffsetX + collectionView.bounds.width
        // 2.滚动该位置
        collectionView.setContentOffset(CGPoint(x: offsetX, y: 0), animated: true)
    }
}

创建空的Xib,与RecommendCycleView关联



2、UICollectionViewCell

import UIKit
import Kingfisher
class CollectionViewCycleCell: UICollectionViewCell {

    @IBOutlet weak var titleLb: UILabel!
    @IBOutlet weak var iconImageView: UIImageView!
    /** 定义模型属性 */
    var cycleModel:CycleModel? {
        didSet{
            titleLb.text = cycleModel?.title
            guard let iconURL = URL(string: (cycleModel?.imageUrl)!) else {return}
            iconImageView.kf.setImage(with: iconURL, placeholder: Image(named: "Img_default"), options: nil, progressBlock: nil, completionHandler: nil)
        }
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
        
    }

}

UICollectionViewCell关联的Xib



3、数据模型

import UIKit
/** 轮播图模型 */
class CycleModel: NSObject {
    /** 标题 */
    var title: String?
    /** 图片地址 */
    var imageUrl: String?
    
    // MARK:- 自定义构造函数
    init(dict: [String: NSObject]) {
        super.init()
        setValuesForKeys(dict)
    }
    override func setValue(_ value: Any?, forUndefinedKey key: String) {
        
    }
}

新建plist,造假数据



4、viewModel中模拟网络请求,请求假数据

// 请求无限录播图数据
    func requestCycleDate(finishCallback: @escaping() -> ()) {
        
        // 1、获取本地plis
        let cycleData = Bundle.main.path(forResource: "CycleView", ofType: "plist")
        let cycleDict = NSDictionary(contentsOfFile: cycleData!)
        // 2、校验
        guard let cycleDic = cycleDict as? [String: NSObject] else {return}
        guard let cycleArr = cycleDic["data"] as? [[String: NSObject]] else {return}
        // 3、数组字典转模型
        for dict in cycleArr {
            let cycle = CycleModel(dict: dict)
            self.cycleModelArr.append(cycle)
        }
        // 4、完成回调。PS:因为轮播图无法请求,所以使用假数据,正常应该是这次位置请求轮播图的数据进行解析储存
        finishCallback()
    }

5、调用!在需要的控制器中懒加载无限轮播控件,前提先懒加载viewModel(专门请求数据的类)

1>、懒加载viewModel

/** viewModel */
    fileprivate lazy var recommendVM: RecommendViewModel = RecommendViewModel()

2>、懒加载轮播器

/** 轮播器(添加到collectionView上) */
    fileprivate lazy var cycleView:RecommendCycleView = {
        let cycleView = RecommendCycleView.recommendCycleView()
        cycleView.frame = CGRect(x: 0, y: -(kcycleViewH + kGameViewH), width: kScreenW, height: kcycleViewH)
        return cycleView
    }()

3>、赋值

//  通过MVVM的属性调用 请求轮播数据
        recommendVM.requestCycleDate {
            // 传值给轮播View的 cycleModel 的属性,在uiview中给图片赋值
            self.cycleView.cycleModelArr = self.recommendVM.cycleModelArr
        }
上一篇下一篇

猜你喜欢

热点阅读