swift ScrollView之無限輪播(待調整)

輪播是最常看到的廣告方式,但是都沒有實際想過是怎麼辦到的,希望有機會生出想做的collectionView版本。

。我的輪播有幾個Point:

1.在有限的圖片中,一直播下去不會回頭,∴達到無限輪播錯覺。

2.使用timer讓做自行運作,不需手動輪播。

3.透過人力介入,可以改變播放方向。

4.人力介入撥動不到一整頁,也可以改變播放方向。

。原理:

要在原本的照片牆頭尾都另外加上假照片,3-123-1 的方式,這樣在滑到最後一張位置四(真3)時,可以繼續滑到位置五(假1),但我們在滑到位置五(假1)時轉成位置二的(真1)

∵兩張照片相同,故使用者視覺上不會發現。

如果兩張照片不同,就會有視覺差別:如下

設計方式

— Demo不同照片:

— Demo相同照片:

。動手做:

1.設置ScrollView、PageControl

在Main.storyboard 準備ScrollView,其大小與螢幕四邊貼齊。

— ScrollView

內容物大小:

 寬:ScrollView寬*照片數量(+假的兩張)、高:螢幕高

起始點:

(x:距離一個ScrollView寬開始、y:0)     ∵從位置二跑

Delegate:

//因為等等利用UIScrollViewDelegate的內建方法scrollView.delegate = self

並貼上UIPageControl,其isUserInteractionEnabled = false

並設定PageControl.numberOfPages = 幾張照片

PageControl.currentPage = 0 設定當前為第一個點點

Main.storyboard

(也可以用Programmatically方式設定)

2.設置ImageView

Concept:將準備的照片名稱放進array,把他們做成UIImageView丟進ScrollView裡面

創建一組3 -123–1 。。N+2 個imageView

— 每張UIImageView大小

(x:該照片距離幾個螢幕寬、y:0、寬:螢幕寬、高:螢幕高)

頭尾的照片放自訂的,其他按照順序生成放置

— 加入scrollView

scrollView.addSubview(imageView)  

(也可以在Main.storyboard手動設定,可利用stackView讓他們更整齊)

3. 滑動事件

Concept:利用UIScrollViewDelegate的方法scrollViewDidScroll( )偵測滑動事件。

此事件不管是timer觸發ScrollView移動還是手動滑動,皆會被偵測到

— 利用假照片來動手腳:

。如果滑到位置一(假3) 需轉成 位置四(真3),故為三張寬間距

if offsetX == 0 {  let contentOffsetMinX = scrollViewWidth * CGFloat(imageNames.count)  adScrollView.contentOffset = CGPoint(x: contentOffsetMinX, y: 0)   //此時需記錄更改後的最後位置,待會使用
lastTimeOffsetX = contentOffsetMinX
}

/

。如果滑到位置五(假1) 需轉成 位置二(真1) ,故為一張寬間距

if offsetX == scrollViewWidth * CGFloat(imageNames.count + 1) {   adScrollView.contentOffset = CGPoint(x: scrollViewWidth, y: 0)   //此時需記錄更改後的最後位置,待會使用
lastTimeOffsetX = scrollViewWidth
}

如果 lastTimeOffset直接取用ScrollView.contentOffset,有可能來不及變動就取到,可能會是錯誤的位置。

/

。平時滑動時,計算頁數,變動PageControl的currentPage頁碼

//但因為位置一(假3)不列入頁數計算,故需要減一,否則頁數會有誤
let page = round(scrollView.contentOffset.x / scrollViewWidth) — 1
adPageControl.currentPage = Int(page)

/

=以上動手腳都不能用setContentOffset(_:),因為自帶動畫效果,會被發現

4.Timer設置

Concept:

— timer生成前:

不外乎先判斷是否已經生成,當然被生成過就停掉它

確認當下沒有timer時,再生成新的timer

/

— timer生成後:

記得離開頁面時,停掉timer計時器

Timer 如同執行緒,若沒停止的話,是會一直在背景執行的,所以必須在離開畫面時去停止它作法:

生成Timer,並每幾秒觸發滑動func,repeats: true

觸發func autoScroll( )

宣告一個變數,用來儲存當前間隔距離

//預設viewDidAppear( )後,畫面往右邊行走,故我間距初始值為1(位置二)
var index = 1 //間距

每次timer呼叫時,

— 右邊:間距數都加1

— 左邊:間距數都減1

case .right:   //當i跑到間距四個時,也就是第五張圖,從間距2開始跑   if index == imageNames.count + 1 {      index = 2   } else {     index += 1    }

— 右邊

平時每次+1 ,

終究會加到位置五(間距4格)=位置二,下一張為位置三(間距2格)

故間距又回到2開始累加

case .left:  //當跑到位置0時,也就是要從間距2開始跑  if index == 0 {  index = imageNames.count — 1  } else {    index -= 1  }

— 左邊

平時每次-1,

終究會扣到位置一(間距0格)=位置四,下一張為屁股數來第二張,count-1(間距2格)

故間距又回到2開始累扣

adScrollView.setContentOffset(CGPoint(x: scrollViewWidth * CGFloat(index), y: 0), animated: true)//此時需記錄更改後的最後位置,待會使用
lastTimeOffsetX = scrollViewWidth * CGFloat(index)

此時將scrollView位置每次改變(x:螢幕寬*間距數),用動畫效果方式轉換位置比較好看

/

如果沒有用動畫的方式,會沒有滑動的效果

adScrollView.contentOffset = CGPoint(x: scrollViewWidth * CGFloat(spacing), y: 0)

5.當手動介入時

Concept:需要暫停timer,直到手離開畫面時,重新啟動timer,

手動介入若有方向變動,須記錄方向,讓timer知道下次往哪個方向啟動

手動介入觸發func 順序//in scrollViewWillBeginDragging//in scrollViewDidScroll//in scrollViewWillEndDragging 將開始停止拖拽的時候執行//in scrollViewDidEndDragging 停止拖拽的時候開始執行//in scrollViewDidEndDecelerating 减速停止的時候開始執行

故需要在

— scrollViewWillBeginDragging 時,直接終止timer(我發現沒有暫停的func,只能整個Stop掉)

func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {  //print(“\(scrollView.contentOffset.x)”)  stopTimer()}

— 將在即將結束拖曳or結束拖曳時,將位置與上次最後位置相比,取得方向

func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
//print(“\(scrollView.contentOffset.x)”)
guard let lastTimeOffsetX = lastTimeOffsetX else { return } let currentOffsetX = scrollView.contentOffset.x //若有改變方向,變動方向註記 if currentOffsetX < lastTimeOffsetX { direction = .left //往左跑 } else { direction = .right //往右跑 } }

— 在位移結束後,再啟動timer,並重計當下頁數,紀錄最後位置

timer若在位移結束前啟動,會發生跳頁的狀況,因為他已經偷偷先幫你數好了

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {  //print(“\(scrollView.contentOffset.x)”)
let contentOffsetMinX = scrollView.contentOffset.x index = Int(contentOffsetMinX / screenWidth)
//重新計算頁數
lastTimeOffsetX = scrollView.contentOffset.x
//若最後真的有換頁,再重新assign進去
startTimer()}

。Demo:

。彼得潘飛哪裡,我就飛哪裡~

(他說他想在文章裡面出現)

**因為gist 貼上來顯示不出來,我只好用截圖的方式貼了0.0

--

--

奇妙仙子
彼得潘的 Swift iOS / Flutter App 開發教室

When you want something, all the universe conspires in helping you to achieve it.