iOS 最新Swift+UICollectionView實現圖片無限輪播器

Bg:
圖片輪播器數不勝數,但大多是UIScrollView + OC實現的,心血來潮,決定用Swift+UICollectionView造個輪子玩玩HHScrollView:https://github.com/wanghhh/HHScrollView#hhscrollview
先看下效果圖:

Untitled.gif

功能實現:

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"的位置,如下圖:

QQ20170820-2@2x.png

2.當collectionView滾動到最后一張的時候,即滾到"圖片3-2"的位置時,讓collectionView回到"圖片3-1"的位置,這樣就可以繼續向右滾動了。同理,當collectionView滾動到第一張的時候,即滾到"圖片1-1"的位置時,讓collectionView回到"圖片1-2"的位置,這樣就可以繼續向左滾動了。如下圖:

QQ20170820-1@2x.png

以上就是無限輪播的基本實現原理了。
關鍵代碼:

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一下。
文辭粗淺,對于代碼中可能存在的問題,歡迎大家指出,共同學習進步。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容