菜鳥iOS工程師上工第二彈|CollectionView 置中滑動 自動輪播

Betty Pan
8 min readApr 5, 2022

--

collectionView auto scroll, center alignment

距離上工第一彈,時隔半年終於生出了第二彈 🙌🏻
成功轉職剛滿十個月,幸運地度過了每天提心吊膽自己的能力是否能勝任這份工作及為了更快上手專案而自願無止盡加班的過渡期,對iOS工程師這份工作也開始產生其他更多的想法。

看著微量上升的 followers 人數,和閒置已久的medium 趕緊督促自己發發文章了 ☺️ 這裡不僅紀錄 code 也希望能給和我一樣半路出家,轉職 iOS 工程師的你/妳們一些能量,我可以,你們一定也可以 ❤️

🔍 OKay!進入正題 ⋯

如圖 自動輪播置中的 collectionView, 原來要讓每次滑動cell時置中(左右需各顯示出cell頭/尾),要動這麼多手腳啊 🤯

一、 畫面組成:

在第一筆資料前依序加上最後兩筆資料,在最後一筆資料後方依序加入資料頭兩筆。形成

為什麼是前後各加入*兩筆*資料?
右滑,滑到第五筆,偷換到第二筆
左滑,滑到第一筆,偷換到第四筆

為避免滑到最後一筆在尚未完成偷切換時,顯示空空的下一位資料,因此需各加入兩筆,維持資料左右方都能有資料顯示。

有圖有真相 :

左圖為各加兩筆,右圖則為各加一筆
右圖在滑到資料4時(最後一筆),在偷換前會有一下子空空的gap…

( 圖為自動輪播狀態,若使用者使用手動,滑到最後一筆資料時,空空的gap會更為明顯 😶‍🌫️。 )

左:資料前後各加兩筆 / 右:資料前後各加一筆

二、 自動輪播:

設置Timer 使自動輪播每兩秒update一次。

timer = Timer.scheduledTimer(
timeInterval: 2,
target: self,
selector: #selector(update),
userInfo: nil,
repeats: true
)

func update 每次更新往後滑一頁並置中。
使用 tuple currentIndex 紀錄當下頁數與 X point。

@objc func update() {
currentIndex.page += 1

let pageX = CGFloat(currentIndex.page)*(itemWidth+itemSpacing)
let screenWidth = UIScreen.main.bounds.width
let centerX = (screenWidth-CGFloat(itemWidth))/2
currentIndex.x = pageX-centerX
collectionView.setContentOffset(CGPoint(
x: page-centerX,
y: 0), animated: true
)
}

三、 輪播偷切換資料位置:

使用 UIScrollViewDelegate 設置切換位置

觸發條件:
1. 自動/手動皆觸發:
觸發scrollViewDidScroll,在此紀錄滑動之到達頁數。

2. 手動觸發:
scrollViewWillEndDragging/scrollViewDidEndDecelerating
在此設置scrollView手動滑動後,停下之位置及記錄每次結束滑動時頁數。

func scrollViewDidScroll(_ scrollView: UIScrollView) {
setCurrentIndex(
scrollToPage: currentIndex.page,
x: scrollView.contentOffset.x
)
}
func setCurrentIndex(scrollToPage: Int, x: CGFloat) {

switch scrollToPage {
case 1:
guard x <= currentIndex.x else { return }
let lastTwoPageX = CGFloat(colors.count-3)*(itemWidth+itemSpacing)
let centerX = (width-CGFloat(itemWidth))/2
collectionView.setContentOffset(CGPoint(
x: lastTwoPageX-centerX,
y: 0), animated: false
)
currentIndex.page = colors.count-3 case items.count - 2:
guard x >= currentIndex.x else { return }
let SecondPageX = 2*(itemWidth+itemSpacing)
let centerX = (width-CGFloat(itemWidth))/2
collectionView.setContentOffset(CGPoint(
x: SecondPageX-centerX,
y: 0), animated: false
)
currentIndex.page = 2 default: // 其他頁數,只紀錄 index
currentIndex.page = scrollToPage
}
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {    let pageWidth = Float(itemWidth + itemSpacing)
let targetXContentOffset = Float(targetContentOffset.pointee.x)
let contentWidth = Float(collectionView!.contentSize.width)
var newPage = Float(currentIndex.page)
if velocity.x == 0 {
newPage = floor((targetXContentOffset - pageWidth / 2) / pageWidth) + 1.0
} else {
///右滑頁數+1 / 左滑頁數-1
newPage = Float(velocity.x > 0 ? CGFloat(self.currentIndex.page + 1) : CGFloat(self.currentIndex.page - 1))
///第一筆
if newPage < 0 {
newPage = 0
}
///最後一筆
if (Int(newPage) > colors.count - 2) {
newPage = ceil(contentWidth / pageWidth) - 1.0
}
}
let pageX = CGFloat(newPage * pageWidth)
let centerX = (width-CGFloat(itemWidth))/2
///校正 ContentOffset X 停下位置
let point = CGPoint(
x: pageX-centerX,
y: targetContentOffset.pointee.y
)
targetContentOffset.pointee = point
currentIndex.x = point.x
currentIndex.page = Int(newPage)
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let pageWidth = Float(itemWidth + itemSpacing)
let scrollX = Float(scrollView.contentOffset.x)
let page = floor((scrollX - pageWidth / 2) / pageWidth) + 1.0
currentIndex.page = Int(page)
}

以上,打完收工 💻

--

--