
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- 系統(tǒng)回調(從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:- 提供一個快速創(chuàng)建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")
}
// 監(jiān)聽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)
}
// 監(jiān)聽當手動拖拽的時候,移除定時器
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
removeCycleTimer()
}
// 監(jiān)聽當手動拖拽結束時,添加定時器
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)
// 把定時器加入 運行循環(huán)中
RunLoop.main.add(cycleTime!, forMode: RunLoopMode.commonModes)
}
/** 刪除計時器 */
fileprivate func removeCycleTimer() {
cycleTime?.invalidate() // 從運行循環(huán)中移除
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)
}
}
創(chuàng)建空的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
}