Bg:
圖片輪播器數不勝數,但大多是UIScrollView + OC實現的,心血來潮,決定用Swift+UICollectionView造個輪子玩玩HHScrollView:https://github.com/wanghhh/HHScrollView#hhscrollview。
先看下效果圖:
功能實現:
1、Swift+UICollectionView實現自動無限輪播,可手動拖動
2、頁碼顯示,可以自定義頁碼指示器位置、顏色
3、輪播間隔時間等屬性設置
輪播器調用方法:
下載demo,直接將HHScrollView.swift文件拖進自己項目即可。
然后在控制器的viewDidLoad() 中實例化:
//準備圖片數據,就是圖片url字符串
imageDataSource = loadImages()
//提供兩種實例化方法:
//1.通過frame和imageUrls
//let scrollView = HHScrollView.init(frame: CGRect.init(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 200), imageUrls: imageDataSource)
//2.通過frame,后根據網絡數據設置imgUrls
let scrollView = HHScrollView.init(frame: CGRect.init(x: 0, y: 64, width: UIScreen.main.bounds.width, height: 200))
//設置數據源(圖片urlStr)******
//加載本地圖片
//scrollView.isFromNet = false
//scrollView.imgUrls = ["ic_banner01","ic_banner02","ic_banner03"]
//默認加載網絡圖片
scrollView.imgUrls = imageDataSource
//設置代理,根據需要要不要監聽圖片點擊
scrollView.hhScrollViewDelegae = self
HHScrollView提供的屬性:
//代理
weak var hhScrollViewDelegae:HHScrollViewDelegate?
//分頁指示器頁碼顏色
var pageControlColor:UIColor?
//分頁指示器當前頁顏色
var currentPageControlColor:UIColor?
//分頁指示器位置
var pageControlPoint:CGPoint?
//分頁指示器
fileprivate var pageControl:UIPageControl?
//自動滾動時間默認為3.0
var autoScrollDelay:TimeInterval = 3 {
didSet{
removeTimer()
setUpTimer()
}
}
//圖片是否來自網絡,默認是
var isFromNet:Bool = true
//占位圖
var placeholderImage:String = "ic_place"
//設置圖片資源url字符串。
var imgUrls = NSArray(){
didSet{
pageControl?.numberOfPages = imgUrls.count
itemCount = imgUrls.count
self.reloadData()
}
}
fileprivate var itemCount:NSInteger = 0//cellNum
fileprivate var timer:Timer?//定時器
可以通過以上屬性和自身項目需要自定義輪播器的樣式、滾動時間間隔等,這些基本屬性都有默認值。
HHScrollView提供的便利構造器:
//便利構造方法
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的復用機制,不用自己再去考慮imageView的復用問題,節省內存,有利于性能提升。
先說下大致思路:
我們知道UICollectionView繼承自UIScrollView,也就是說UIScrollView的基本屬性方法UICollectionView都有,那么UICollectionView也可以分頁顯示。將item(UITableView對應的cell)的寬和高分別設置成UICollectionView自身的寬和高,數據源返回的item個數就是參與圖片的圖片個數,那么問題就在于當滾動到最后一張或第一張圖片的時候,怎么繼續滾動呢?
為了解決這個問題,我們可以通過擴大item的個數的方法解決它,無限輪播的關鍵就在于此:
1.將數據源方法返回的item個數設置未imgUrls.count(imgUrls是網絡圖片url或本地圖片的數組)的2倍,在collectionView加載完成后默認滾動到索引為imgUrls.count的位置,這樣cell就可以向左或右滾動了。
例如:我們想加載3張圖片,那么collectionView:初始位置應該在"圖片1-2"的位置,如下圖:
2.當collectionView滾動到最后一張的時候,即滾到"圖片3-2"的位置時,讓collectionView回到"圖片3-1"的位置,這樣就可以繼續向右滾動了。同理,當collectionView滾動到第一張的時候,即滾到"圖片1-1"的位置時,讓collectionView回到"圖片1-2"的位置,這樣就可以繼續向左滾動了。如下圖:
以上就是無限輪播的基本實現原理了。
關鍵代碼:
1.collectionView初始位置設置:
//在collectionView加載完成后默認滾動到索引為imgUrls.count的位置,這樣cell就可以向左或右滾動
DispatchQueue.main.async {
//注意:在輪播器視圖添加到控制器的view上以后,這樣是為了將分頁指示器添加到self.superview上(如果將分頁指示器直接添加到collectionView上的話,指示器將不能正常顯示)
self.setUpPageControl()
let indexpath = NSIndexPath.init(row: self.imgUrls.count, section: 0)
//滾動位置
self.scrollToItem(at: indexpath as IndexPath, at: .left, animated: false)
}
此段代碼寫在collectionView的init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout)方法中,關鍵在于要等到在collectionView加載完成以后,再去改變滾動的位置,這里利用DispatchQueue.main.async異步實現。本質就是利用主隊列調度任務的阻塞特性實現,因為主隊列只會在主線程"閑暇"的時候才去執行別的任務,這里"閑暇"就是指collectionView加載完成以后。
2.UIPageControl的加載時機和方式
要想將頁碼顯示器封裝到輪播器中,而不是在使用輪播器的控制器中創建和加載,做到更好的封裝,也將setUpPageControl的創建頁碼器的代碼放在init()方法的主隊列異步方法中去,在上面代碼中可以看到self.setUpPageControl()。創建代碼如下:
@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!)
}
另外發現將UIPageControl直接add到collectionView上時不能正常顯示,這個問題還沒有研究,有知道的大神可以告訴我哈O(∩_∩)O~~,這里解決方法是,add到collectionView的superview上,在init的方法中要想獲取到collectionView的superview,只能等到collectionView加載完成也就是添加到控制器的view上以后。這也是將創建方法放在DispatchQueue.main.async{}方法中的原因。也就做到了等collectionView被添加到控制器的view上以后才去創建pageControl。
3.手動無限滾動實現:在于拖動時,collectionView滾動位置的控制,在scrollView滾動減速的代理方法中:
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
//當前的索引
var offset:NSInteger = NSInteger(scrollView.contentOffset.x / scrollView.bounds.size.width)
//第0頁時,跳到索引imgUrls.count位置;最后一頁時,跳到索引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)
}
關鍵點就是上面原理中說的改變contentOffset或者滾動位置: 第0頁時,跳到索引imgUrls.count位置;最后一頁時,跳到索引imgUrls.count-1位置
4.自動輪播實現:
首先,在init()調用創建定時器,去觸發自動滾動方法:
@objc private func setUpTimer(){
timer = Timer.init(timeInterval: autoScrollDelay, target: self, selector: #selector(autoScroll), userInfo: nil, repeats: true)
RunLoop.current.add(timer!, forMode: .commonModes)
}
自動滾動方法autoScroll的實現:
//當前的索引
var offset:NSInteger = NSInteger(self.contentOffset.x / self.bounds.size.width)
//第0頁時,跳到索引imgUrls.count位置;最后一頁時,跳到索引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)
//再滾到下一頁
self.setContentOffset(CGPoint.init(x: CGFloat(offset + 1) * self.bounds.size.width, y: 0), animated: true)
}else{
//直接滾到下一頁
self.setContentOffset(CGPoint.init(x: CGFloat(offset + 1) * self.bounds.size.width, y: 0), animated: true)
}
此方法關鍵點在于:當滾動到第0頁和最后一頁時要做特殊處理,比如當滾到最后一頁時,要先把contentOffset設置為imgUrls.count-1位置,然后再動畫改變contentOffset到imgUrls.count位置,這樣就實現了視覺上的平滑滾動效果了。
5.定時器的添加與移除控制:
//拖動停止時添加定時器
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
setUpTimer()
}
//將要拖動時移除
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
removeTimer()
}
//添加定時器
@objc private func setUpTimer(){
timer = Timer.init(timeInterval: autoScrollDelay, target: self, selector: #selector(autoScroll), userInfo: nil, repeats: true)
RunLoop.current.add(timer!, forMode: .commonModes)
}
//移除定時器
@objc private func removeTimer(){
if (timer != nil) {
timer?.invalidate()
timer = nil
}
}
//輪播器銷毀時也要移除
deinit {
removeTimer()
}
6.自定義CollectionViewFlowLayout
class HHCollectionViewFlowLayout:UICollectionViewFlowLayout{
//prepare方法在collectionView第一次布局的時候被調用
override func prepare() {
super.prepare()//必須寫
collectionView?.backgroundColor = UIColor.white
// 通過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 //分頁
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 {
//點擊代理方法
@objc optional func hhScrollView(_ scrollView: HHScrollView, didSelectRowAt index: NSInteger)
}
通過代理可以監聽被點擊的圖片的索引。
好了,到此Swift+UICollectionView實現圖片無限輪播器主要過程介紹完了,詳細代碼請查看demo:下載地址:https://github.com/wanghhh/HHScrollView#hhscrollview。demo中下載圖片用了SDWebImage,運行前請cocoaPods install一下。
文辭粗淺,對于代碼中可能存在的問題,歡迎大家指出,共同學習進步。