Bg:
圖片輪播器數(shù)不勝數(shù)般码,但大多是UIScrollView + OC實(shí)現(xiàn)的奥秆,心血來(lái)潮,決定用Swift+UICollectionView造個(gè)輪子玩玩HHScrollView:https://github.com/wanghhh/HHScrollView#hhscrollview。
先看下效果圖:
功能實(shí)現(xiàn):
1呵燕、Swift+UICollectionView實(shí)現(xiàn)自動(dòng)無(wú)限輪播,可手動(dòng)拖動(dòng)
2、頁(yè)碼顯示,可以自定義頁(yè)碼指示器位置狭吼、顏色
3、輪播間隔時(shí)間等屬性設(shè)置
輪播器調(diào)用方法:
下載demo,直接將HHScrollView.swift文件拖進(jìn)自己項(xiàng)目即可殖妇。
然后在控制器的viewDidLoad() 中實(shí)例化:
//準(zhǔn)備圖片數(shù)據(jù)刁笙,就是圖片url字符串
imageDataSource = loadImages()
//提供兩種實(shí)例化方法:
//1.通過(guò)frame和imageUrls
//let scrollView = HHScrollView.init(frame: CGRect.init(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 200), imageUrls: imageDataSource)
//2.通過(guò)frame,后根據(jù)網(wǎng)絡(luò)數(shù)據(jù)設(shè)置imgUrls
let scrollView = HHScrollView.init(frame: CGRect.init(x: 0, y: 64, width: UIScreen.main.bounds.width, height: 200))
//設(shè)置數(shù)據(jù)源(圖片urlStr)******
//加載本地圖片
//scrollView.isFromNet = false
//scrollView.imgUrls = ["ic_banner01","ic_banner02","ic_banner03"]
//默認(rèn)加載網(wǎng)絡(luò)圖片
scrollView.imgUrls = imageDataSource
//設(shè)置代理谦趣,根據(jù)需要要不要監(jiān)聽(tīng)圖片點(diǎn)擊
scrollView.hhScrollViewDelegae = self
HHScrollView提供的屬性:
//代理
weak var hhScrollViewDelegae:HHScrollViewDelegate?
//分頁(yè)指示器頁(yè)碼顏色
var pageControlColor:UIColor?
//分頁(yè)指示器當(dāng)前頁(yè)顏色
var currentPageControlColor:UIColor?
//分頁(yè)指示器位置
var pageControlPoint:CGPoint?
//分頁(yè)指示器
fileprivate var pageControl:UIPageControl?
//自動(dòng)滾動(dòng)時(shí)間默認(rèn)為3.0
var autoScrollDelay:TimeInterval = 3 {
didSet{
removeTimer()
setUpTimer()
}
}
//圖片是否來(lái)自網(wǎng)絡(luò),默認(rèn)是
var isFromNet:Bool = true
//占位圖
var placeholderImage:String = "ic_place"
//設(shè)置圖片資源url字符串疲吸。
var imgUrls = NSArray(){
didSet{
pageControl?.numberOfPages = imgUrls.count
itemCount = imgUrls.count
self.reloadData()
}
}
fileprivate var itemCount:NSInteger = 0//cellNum
fileprivate var timer:Timer?//定時(shí)器
可以通過(guò)以上屬性和自身項(xiàng)目需要自定義輪播器的樣式、滾動(dòng)時(shí)間間隔等前鹅,這些基本屬性都有默認(rèn)值摘悴。
HHScrollView提供的便利構(gòu)造器:
//便利構(gòu)造方法
convenience init(frame:CGRect) {
self.init(frame: frame, collectionViewLayout: HHCollectionViewFlowLayout.init())
}
convenience init(frame:CGRect,imageUrls:NSArray) {
self.init(frame: frame, collectionViewLayout: HHCollectionViewFlowLayout.init())
imgUrls = imageUrls
}
基本原理:
充分利用UICollectionView的cell的復(fù)用機(jī)制,不用自己再去考慮imageView的復(fù)用問(wèn)題舰绘,節(jié)省內(nèi)存蹂喻,有利于性能提升。
先說(shuō)下大致思路:
我們知道UICollectionView繼承自UIScrollView除盏,也就是說(shuō)UIScrollView的基本屬性方法UICollectionView都有叉橱,那么UICollectionView也可以分頁(yè)顯示。將item(UITableView對(duì)應(yīng)的cell)的寬和高分別設(shè)置成UICollectionView自身的寬和高者蠕,數(shù)據(jù)源返回的item個(gè)數(shù)就是參與圖片的圖片個(gè)數(shù),那么問(wèn)題就在于當(dāng)滾動(dòng)到最后一張或第一張圖片的時(shí)候掐松,怎么繼續(xù)滾動(dòng)呢踱侣?
為了解決這個(gè)問(wèn)題,我們可以通過(guò)擴(kuò)大item的個(gè)數(shù)的方法解決它大磺,無(wú)限輪播的關(guān)鍵就在于此:
1.將數(shù)據(jù)源方法返回的item個(gè)數(shù)設(shè)置未imgUrls.count(imgUrls是網(wǎng)絡(luò)圖片url或本地圖片的數(shù)組)的2倍抡句,在collectionView加載完成后默認(rèn)滾動(dòng)到索引為imgUrls.count的位置,這樣cell就可以向左或右滾動(dòng)了杠愧。
例如:我們想加載3張圖片待榔,那么collectionView:初始位置應(yīng)該在"圖片1-2"的位置,如下圖:
2.當(dāng)collectionView滾動(dòng)到最后一張的時(shí)候流济,即滾到"圖片3-2"的位置時(shí)锐锣,讓collectionView回到"圖片3-1"的位置,這樣就可以繼續(xù)向右滾動(dòng)了绳瘟。同理雕憔,當(dāng)collectionView滾動(dòng)到第一張的時(shí)候,即滾到"圖片1-1"的位置時(shí)糖声,讓collectionView回到"圖片1-2"的位置斤彼,這樣就可以繼續(xù)向左滾動(dòng)了分瘦。如下圖:
以上就是無(wú)限輪播的基本實(shí)現(xiàn)原理了。
關(guān)鍵代碼:
1.collectionView初始位置設(shè)置:
//在collectionView加載完成后默認(rèn)滾動(dòng)到索引為imgUrls.count的位置琉苇,這樣cell就可以向左或右滾動(dòng)
DispatchQueue.main.async {
//注意:在輪播器視圖添加到控制器的view上以后嘲玫,這樣是為了將分頁(yè)指示器添加到self.superview上(如果將分頁(yè)指示器直接添加到collectionView上的話,指示器將不能正常顯示)
self.setUpPageControl()
let indexpath = NSIndexPath.init(row: self.imgUrls.count, section: 0)
//滾動(dòng)位置
self.scrollToItem(at: indexpath as IndexPath, at: .left, animated: false)
}
此段代碼寫(xiě)在collectionView的init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout)方法中并扇,關(guān)鍵在于要等到在collectionView加載完成以后去团,再去改變滾動(dòng)的位置,這里利用DispatchQueue.main.async異步實(shí)現(xiàn)拜马。本質(zhì)就是利用主隊(duì)列調(diào)度任務(wù)的阻塞特性實(shí)現(xiàn)渗勘,因?yàn)橹麝?duì)列只會(huì)在主線程"閑暇"的時(shí)候才去執(zhí)行別的任務(wù),這里"閑暇"就是指collectionView加載完成以后俩莽。
2.UIPageControl的加載時(shí)機(jī)和方式
要想將頁(yè)碼顯示器封裝到輪播器中旺坠,而不是在使用輪播器的控制器中創(chuàng)建和加載,做到更好的封裝扮超,也將setUpPageControl的創(chuàng)建頁(yè)碼器的代碼放在init()方法的主隊(duì)列異步方法中去取刃,在上面代碼中可以看到self.setUpPageControl()。創(chuàng)建代碼如下:
@objc private func setUpPageControl(){
pageControl = UIPageControl.init()
pageControl?.frame = (pageControlPoint != nil) ? CGRect.init(x: (pageControlPoint?.x)!, y: (pageControlPoint?.y)!, width: self.bounds.size.width - (pageControlPoint?.x)!, height: 8) : CGRect.init(x: 0, y: self.frame.maxY - 16, width: self.bounds.size.width, height: 8)
pageControl?.pageIndicatorTintColor = pageControlColor ?? UIColor.lightGray
pageControl?.currentPageIndicatorTintColor = currentPageControlColor ?? UIColor.orange
pageControl?.numberOfPages = imgUrls.count
pageControl?.currentPage = 0
//一定要將指示器添加到superview上
self.superview?.addSubview(pageControl!)
}
另外發(fā)現(xiàn)將UIPageControl直接add到collectionView上時(shí)不能正常顯示出刷,這個(gè)問(wèn)題還沒(méi)有研究璧疗,有知道的大神可以告訴我哈O(∩_∩)O~~,這里解決方法是馁龟,add到collectionView的superview上崩侠,在init的方法中要想獲取到collectionView的superview,只能等到collectionView加載完成也就是添加到控制器的view上以后坷檩。這也是將創(chuàng)建方法放在DispatchQueue.main.async{}方法中的原因却音。也就做到了等collectionView被添加到控制器的view上以后才去創(chuàng)建pageControl。
3.手動(dòng)無(wú)限滾動(dòng)實(shí)現(xiàn):在于拖動(dòng)時(shí)矢炼,collectionView滾動(dòng)位置的控制系瓢,在scrollView滾動(dòng)減速的代理方法中:
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
//當(dāng)前的索引
var offset:NSInteger = NSInteger(scrollView.contentOffset.x / scrollView.bounds.size.width)
//第0頁(yè)時(shí),跳到索引imgUrls.count位置句灌;最后一頁(yè)時(shí)夷陋,跳到索引imgUrls.count-1位置
if offset == 0 || offset == (self.numberOfItems(inSection: 0) - 1) {
if offset == 0 {
offset = imgUrls.count
}else {
offset = imgUrls.count - 1
}
}
scrollView.contentOffset = CGPoint.init(x: CGFloat(offset) * scrollView.bounds.size.width, y: 0)
}
關(guān)鍵點(diǎn)就是上面原理中說(shuō)的改變contentOffset或者滾動(dòng)位置: 第0頁(yè)時(shí),跳到索引imgUrls.count位置胰锌;最后一頁(yè)時(shí)骗绕,跳到索引imgUrls.count-1位置
4.自動(dòng)輪播實(shí)現(xiàn):
首先,在init()調(diào)用創(chuàng)建定時(shí)器匕荸,去觸發(fā)自動(dòng)滾動(dòng)方法:
@objc private func setUpTimer(){
timer = Timer.init(timeInterval: autoScrollDelay, target: self, selector: #selector(autoScroll), userInfo: nil, repeats: true)
RunLoop.current.add(timer!, forMode: .commonModes)
}
自動(dòng)滾動(dòng)方法autoScroll的實(shí)現(xiàn):
//當(dāng)前的索引
var offset:NSInteger = NSInteger(self.contentOffset.x / self.bounds.size.width)
//第0頁(yè)時(shí)爹谭,跳到索引imgUrls.count位置;最后一頁(yè)時(shí)榛搔,跳到索引imgUrls.count-1位置
if offset == 0 || offset == (itemCount - 1) {
if offset == 0 {
offset = imgUrls.count
}else {
offset = imgUrls.count - 1
}
self.contentOffset = CGPoint.init(x: CGFloat(offset) * self.bounds.size.width, y: 0)
//再滾到下一頁(yè)
self.setContentOffset(CGPoint.init(x: CGFloat(offset + 1) * self.bounds.size.width, y: 0), animated: true)
}else{
//直接滾到下一頁(yè)
self.setContentOffset(CGPoint.init(x: CGFloat(offset + 1) * self.bounds.size.width, y: 0), animated: true)
}
此方法關(guān)鍵點(diǎn)在于:當(dāng)滾動(dòng)到第0頁(yè)和最后一頁(yè)時(shí)要做特殊處理诺凡,比如當(dāng)滾到最后一頁(yè)時(shí)东揣,要先把contentOffset設(shè)置為imgUrls.count-1位置,然后再動(dòng)畫(huà)改變contentOffset到imgUrls.count位置腹泌,這樣就實(shí)現(xiàn)了視覺(jué)上的平滑滾動(dòng)效果了嘶卧。
5.定時(shí)器的添加與移除控制:
//拖動(dòng)停止時(shí)添加定時(shí)器
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
setUpTimer()
}
//將要拖動(dòng)時(shí)移除
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
removeTimer()
}
//添加定時(shí)器
@objc private func setUpTimer(){
timer = Timer.init(timeInterval: autoScrollDelay, target: self, selector: #selector(autoScroll), userInfo: nil, repeats: true)
RunLoop.current.add(timer!, forMode: .commonModes)
}
//移除定時(shí)器
@objc private func removeTimer(){
if (timer != nil) {
timer?.invalidate()
timer = nil
}
}
//輪播器銷毀時(shí)也要移除
deinit {
removeTimer()
}
6.自定義CollectionViewFlowLayout
class HHCollectionViewFlowLayout:UICollectionViewFlowLayout{
//prepare方法在collectionView第一次布局的時(shí)候被調(diào)用
override func prepare() {
super.prepare()//必須寫(xiě)
collectionView?.backgroundColor = UIColor.white
// 通過(guò)collectionView 的屬性布局cell
self.itemSize = (self.collectionView?.bounds.size)!
self.minimumInteritemSpacing = 0 //cell之間最小間距
self.minimumLineSpacing = 0 //最小行間距
self.scrollDirection = .horizontal;
self.collectionView?.bounces = false //禁用彈簧效果
self.collectionView?.isPagingEnabled = true //分頁(yè)
self.collectionView?.showsHorizontalScrollIndicator = false
self.collectionView?.showsVerticalScrollIndicator = false
}}
7.自定義HHCollectionViewCell:
class HHCollectionViewCell:UICollectionViewCell {
var imageView:UIImageView?
override init(frame: CGRect) {
super.init(frame: frame)
self.clipsToBounds = true
imageView = UIImageView.init(frame: self.bounds)
imageView?.contentMode = .scaleAspectFill
contentView.addSubview(imageView!)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}}
8.HHScrollView的代理方法:
@objc protocol HHScrollViewDelegate:NSObjectProtocol {
//點(diǎn)擊代理方法
@objc optional func hhScrollView(_ scrollView: HHScrollView, didSelectRowAt index: NSInteger)
}
通過(guò)代理可以監(jiān)聽(tīng)被點(diǎn)擊的圖片的索引。
好了凉袱,到此Swift+UICollectionView實(shí)現(xiàn)圖片無(wú)限輪播器主要過(guò)程介紹完了芥吟,詳細(xì)代碼請(qǐng)查看demo:下載地址:https://github.com/wanghhh/HHScrollView#hhscrollview。demo中下載圖片用了SDWebImage,運(yùn)行前請(qǐng)cocoaPods install一下专甩。
文辭粗淺钟鸵,對(duì)于代碼中可能存在的問(wèn)題,歡迎大家指出涤躲,共同學(xué)習(xí)進(jìn)步棺耍。