
這是一個(gè)圖片輪播的 Demo,上半部分用 CollectionView 實(shí)現(xiàn),沒有無限循環(huán)效果,下半部分是用 ScrollView 實(shí)現(xiàn)的,自動(dòng)無限輪播。代碼地址在這里。
上次用 CollectionView 實(shí)現(xiàn)了一個(gè)多表視圖,這次本來想用同樣的思路實(shí)現(xiàn)個(gè)圖片輪播,結(jié)果發(fā)現(xiàn)并不是很方便。主要是“無限循環(huán)滑動(dòng)”的效果單純用 CollectionView 的接口的話基本做不到,要做也只能是把待顯示圖片的數(shù)量 * N(N是一個(gè)很大的數(shù)),可以做到在比較長的時(shí)間內(nèi)一直向后輪播,因?yàn)?Cell 的復(fù)用機(jī)制,真正使用的只有兩個(gè) Cell 對象,所以不用擔(dān)心內(nèi)存爆炸。但是這樣做不到手勢滑動(dòng)的“首尾連接”,就是無論往左還是往右都可以無限滑動(dòng),要達(dá)到這樣的效果,還是得用到 ScrollView 的接口,所以我覺得還不如直接用 ScrollView 寫好了。
用 ScrollView 實(shí)現(xiàn)“首尾連接”有一種常見的思路:

白色邊框代表 ScrollView 的 frame,藍(lán)色部分是 content。簡單來說就是在要顯示的圖片的左右兩邊各加一張圖片作為橋接。譬如當(dāng)前顯示的是第一張圖片(img1),按理說再用手往右滑動(dòng)的話啥都沒有了,但現(xiàn)在我在img1的左邊又加了一張img3,這樣在向右滑動(dòng)的時(shí)候我們還能看到 img3,然后在滑動(dòng)即將停下的時(shí)候,快速切換到第四個(gè)位置的 img3,所以最左邊的 img3 永遠(yuǎn)不會(huì)在靜止的時(shí)候顯示?,F(xiàn)在如果再向右滑動(dòng)的話,就正?;?img2。向左滑動(dòng)的時(shí)候同理,在滑到最后的那張 img1的一瞬間,切換到第二個(gè)位置的 img1。代碼如下:
extension AutoScrollView: UIScrollViewDelegate {
//速度變慢,即將停下的時(shí)候調(diào)用
func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
//因?yàn)榭梢宰笥一?,所以不能簡單地?,而要通過contentofff計(jì)算要滑到第幾張圖片
let page = Int(scrollView.contentOffset.x / width)
if page == 0 {
scrollView.contentOffset.x = width * CGFloat(imageCount)
} else if page == imageCount + 1 {
scrollView.contentOffset.x = width
}
pageCtrl.currentPage = Int(scrollView.contentOffset.x / width) - 1
}
}
上面說的主要是對手勢滑動(dòng)部分的處理,至于按固定時(shí)間間隔向右輪播更簡單。只要判斷一下當(dāng)前顯示的是否是最后一張圖片(img3),如果是,那下一張就顯示第一張(img1),否則正常顯示下一張圖片:
func slideByTime() {
var page = pageCtrl.currentPage + 1
if page == imageCount {
scrollView.contentOffset.x = width
page = 0
} else {
self.scrollView.contentOffset.x = self.width * CGFloat(page + 1)
}
pageCtrl.currentPage = page
}
然后用一個(gè)計(jì)時(shí)器定時(shí)調(diào)用上面這個(gè)方法就好了。有的同學(xué)可能會(huì)直接調(diào)用NSTimer的scheduledTimerWithTimeInterval方法,但是這個(gè)方法有個(gè)潛在的危險(xiǎn),一旦將target參數(shù)設(shè)為self,repeat參數(shù)設(shè)為true,NSTimer就會(huì)獲取當(dāng)前對象的一個(gè)引用,而且極難打破這個(gè)引用,這樣當(dāng)前對象就不能被正確釋放,極易造成內(nèi)存泄漏。
解決方法是把要執(zhí)行的方法作為一個(gè) block 傳給 NSTimer的userInfo屬性,把target參數(shù)設(shè)為NSTimer自己,給NSTimer增加一個(gè)擴(kuò)展方法sy_procInvoke,在方法體中執(zhí)行userInfo:
typealias Proc = @convention(block) () -> ()
extension NSTimer {
class func sy_scheduledTimeerWithTimeInterval(interval: NSTimeInterval, repeats: Bool, repeatHandler: Proc) {
scheduledTimerWithTimeInterval(interval, target: self, selector: "sy_procInvoke:", userInfo: unsafeBitCast(repeatHandler, AnyObject.self), repeats: true)
}
class func sy_procInvoke(timer: NSTimer) {
let proc = unsafeBitCast(timer.userInfo, Proc.self)
proc()
}
}
因?yàn)?code>userInfo的類型是個(gè) AnyObject,而 Swift 中的閉包是不能轉(zhuǎn)化為AnyObject的,所以得定義一個(gè)block類型,就是這一句typealias Proc = @convention(block) () -> (),然后在傳給userInfo的時(shí)候轉(zhuǎn)化為AnyObject類型。
調(diào)用的時(shí)候用weak修飾self:
//計(jì)時(shí)器
NSTimer.sy_scheduledTimeerWithTimeInterval(1, repeats: true) { [weak self] in
self?.slideByTime()
}
這樣每隔1秒就會(huì)執(zhí)行slideByTime方法,而且NSTimer沒有持有當(dāng)前對象的引用,任務(wù)完成。