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:-
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
}