iOS橫滑元件實現
這是我早先實現的一個自定義橫滑元件,本文回顧一下當時實現過程遇到的問題和細節,最後有原始碼地址
文中所有圖片託管在Github上
所謂橫滑元件其實就如圖所示的效果:
列一下UI上的要求:
- 每次滑動一頁,有pageEnable的效果
- 每次顯示在螢幕中的item其實是三個而不是一個
- 每個item的間距、檢視與螢幕邊緣的邊距嚴格按照UI上樣子
UICollectionView+pageEnable
使用UICollectionView並開啟pageEnable是最容易想到的方案,我們來試一下能否滿足需要
關鍵的幾個引數如下所示
container.width = 375
collectionView.isPagingEnable = true
collectionView.width = 375
leftPadding = rightPadding = 16
cell.width = container.width - leftPadding - rightPadding
collectionView.contentInset = UIEdgeInset(0,16,0,0)
效果如下所示:
顯然,沒有達到預期:
- 問題1,每次滑動停止後,cell的位置不對
- 通過列印contentOffset得知,UIScrollView開啟pagingEnable後的自動翻頁,每次修改contentOffset的值等於UIScrollView.width
- 而且我們無法自定義每次翻頁移動的距離
- 問題2,由於設定了collectionView.contentInset.left,所以第一cell可以移動到螢幕最左邊而不能自動還原到初始位置
不甘心,繼續調整
我畫了一張圖來表示要實現的效果:
- 根據上圖的效果,我們希望的效果是每次移動cell時移動的距離(兩條紅豎線之間的距離)是一個cell的寬度+cell之間的距離--cell.width+interval
- 既然pageEnable特性每次移動的距離一定是scrollView.width,所以我們可以讓scrollView.width = cell.width+interval
- 這或許能解決上面顯示異常問題
我們更新一下配置引數,如下:
leftPadding = rightPadding = 16
container.width = 375
collectionView.isPagingEnable = true
cell.width = container.width - leftPadding - rightPadding
interval = 8
collectionView.width = cell.width + interval
collectionView.contentInset = UIEdgeInset(0,0,0,interval) // 這一句可能會引起你的困惑,但經過測試必須設定成這樣,否則效果有問題,本文不做詳細解釋,跟scrollView自身對於contentSize和contentOffset的調整有關
來看一下效果:
哇,好像不錯!但還是有問題:
- 我們希望同時顯示三個cell,但該效果卻只能顯示1個cell
- 這是因為collectionView的寬度剛好能顯示下一個cell和一個interval,沒有更多空間來顯示其他cell了
這就很尷尬了,為了利用pageEnable的特性,我們不得不修改collectionView的寬度小一些,但這卻導致無法足夠的cell個數
所以,結論是:❌
UICollectionView + UIScrollView
在調研其他技術方案時,受一Paging a overflowing collection view啟發,可以使用一個UICollectionView和一個UIScrollView一同實現類似效果
核心思想如下:
- 單獨用一個UIScrollView,利用pageEnable特性來實現符合要求的橫滑、拖拽翻頁效果
- 單獨用一個UICollectionView來利用它的cell顯示、複用機制
- UIScrollView是不顯示的,只用它的拖拽手勢能力。當拖拽UIScrollView時,將contentOffset的移動應用到UICollectionView中
具體實現過程中有些細節需要注意,比如:
- collectionView的contentInset需要設定
- 將scrollView的移動應用到collectionView中時如何計算準確
- 需要關閉collectionView的panGesture
再放一下效果
結論是:✅
原始碼地址:SlideView.swift
優缺點
優點很明顯:
- 既複用了UIScrollView的pageEnable手勢和動畫效果,也複用了UICollectionView的cell複用機制
- 由於複用了UICollectionView,所以相比通過UIScrollView自定義實現,在一些使用者互動體驗上可能更好,比如在快速橫滑時,自定義的實現可能就沒辦法快速的準備好每一個cell並無縫從上一頁切換過來,可能會有點卡頓
- 所有實現細節都是通過系統官方的public API,不存在任何trick行為,穩定性好
缺點:
在使用者體驗上沒發現缺點。只是在封裝為獨立元件時需要注意更多細節,比如:
- 該元件將CollectionView封裝了起來,所以必須給外部使用者暴露dataSource和delegate等必要的回撥和資料來源方法
使用UIScrollView完全自定義實現
我還看過另一種方案:
- 自己建立cell檢視,新增到UIScrollView上
- 完全由自己來控制cell的複用和顯示邏輯
- 滑動手勢和效果方面,利用UIScrollViewDelegate方法來控制擡起手指後移動到到下一個或上一個cell的效果(該效果我曾經也實現過,可以參考設計與Swipe-Delete不衝突的UIPageViewController)
這個思路看上去應該是可行的,我也看過類似的原始碼實現,是Github上的一個程式碼
但該原始碼的顯示邏輯寫的不好:
- 每次切換cell時,會同時通過delegate要求更新所有的cell資料(顯示在螢幕中的cell和在快取池中未用到的cell)