iOS 最新Swift+UICollectionView實(shí)現(xiàn)圖片無(wú)限輪播器

Bg:
圖片輪播器數(shù)不勝數(shù)般码,但大多是UIScrollView + OC實(shí)現(xiàn)的奥秆,心血來(lái)潮,決定用Swift+UICollectionView造個(gè)輪子玩玩HHScrollView:https://github.com/wanghhh/HHScrollView#hhscrollview
先看下效果圖:

Untitled.gif

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

QQ20170820-2@2x.png

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)了分瘦。如下圖:

QQ20170820-1@2x.png

以上就是無(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)步棺耍。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市种樱,隨后出現(xiàn)的幾起案子蒙袍,更是在濱河造成了極大的恐慌,老刑警劉巖嫩挤,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件害幅,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡岂昭,警方通過(guò)查閱死者的電腦和手機(jī)以现,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)约啊,“玉大人叼风,你說(shuō)我怎么就攤上這事」髌唬” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵茵汰,是天一觀的道長(zhǎng)枢里。 經(jīng)常有香客問(wèn)我,道長(zhǎng)蹂午,這世上最難降的妖魔是什么栏豺? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮豆胸,結(jié)果婚禮上奥洼,老公的妹妹穿的比我還像新娘。我一直安慰自己晚胡,他們只是感情好灵奖,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布嚼沿。 她就那樣靜靜地躺著,像睡著了一般瓷患。 火紅的嫁衣襯著肌膚如雪骡尽。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,737評(píng)論 1 305
  • 那天擅编,我揣著相機(jī)與錄音攀细,去河邊找鬼。 笑死爱态,一個(gè)胖子當(dāng)著我的面吹牛谭贪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播锦担,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼俭识,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了吆豹?” 一聲冷哼從身側(cè)響起鱼的,我...
    開(kāi)封第一講書(shū)人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎痘煤,沒(méi)想到半個(gè)月后凑阶,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡衷快,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年宙橱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蘸拔。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡师郑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出调窍,到底是詐尸還是另有隱情宝冕,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布邓萨,位于F島的核電站地梨,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏缔恳。R本人自食惡果不足惜宝剖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望歉甚。 院中可真熱鬧万细,春花似錦、人聲如沸纸泄。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至仁烹,卻和暖如春耸弄,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背卓缰。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工计呈, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人征唬。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓捌显,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親总寒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子扶歪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容