UIScrollView巢狀的完美解決方案
UIScrollView巢狀的完美解決方案
做iOS開發,不可避免的會遇到UIScrollView的巢狀問題,之前也曾遇到過,吭哧吭哧做完了,效果不理想,和產品大戰好幾回合,就那樣了。不可避免的,又一次遇到了這個問題,就和同事一起研究了一下,徹底解決了這個問題。寫了一個demo,以後再遇到就直接用了。今天主要是總結一下實現難點。免得自己過段時間又忘了,也給有同樣困擾的你一個思路。
需求
如圖:
要求:上滑的時候先滑headerView,headerView滑出螢幕時,tableView吸頂且開始滑動。下滑時先滑tableView,滑到頂部第一個cell出現,則開始滑headerView。 這是一個最簡單的scrollView巢狀需求,後面還會有進階的需求。
具體方案
其實巢狀最大的問題就是手勢衝突問題,上層的ScrollView會攔截手勢,導致手指在上層ScrollView滑動的時候,下層ScrollView不動。所以我們首先要讓手勢衝突時,兩個手勢都去響應。這樣,我們滑動的時候,兩個scrollView都會滑動。
第一步 上層scrollView不攔截手勢
extension TopScrollView: UIGestureRecognizerDelegate { //手勢衝突的時候同時響應 func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } }
第二步 建立上下文物件
上下兩層scrollView滑動時候都需要對方的offset來計算,所以我們建立一個上下文物件,讓兩個scrollView都持有,避免了頻繁正反向傳值的問題。
class SyncScrollContext { var maxOffsetY: CGFloat = 0 //上層最大的滑動距離 var outerOffset: CGPoint = CGPoint.zero //上層offset var innerOffset: CGPoint = CGPoint.zero //下層offset }
第三步 滑動的時候計算滑動優先順序
下層scrollView的contentOffset變化時計算: ~~~ class BottomScrollView: UIScrollView {
class BottomScrollView: UIScrollView { var syncScrollContext: SyncScrollContext? override var contentOffset: CGPoint { didSet { if contentOffset.y != oldValue.y { //下層scrollView滑動 if syncScrollContext.innerOffset.y > 0 { // 上層的scrollView滑動,則下層的scrollView保持最大滑動距離 contentOffset.y = syncScrollContext.maxOffsetY } else { //否則,上層不動,下層滑動 } //同步offset到上下文 syncScrollContext.outerOffset = contentOffset } } } }
上層的scrollView的contentOffset變化時計算:
class TopScrollView: UITableView { var syncScrollContext: SyncScrollContext? override var contentOffset: CGPoint { didSet { if contentOffset.y != oldValue.y { //上層滑動 guard let syncScrollContext = syncScrollContext else { return } if syncScrollContext.outerOffset.y < syncScrollContext.maxOffsetY { //下層的offset < 下層可滑動最大值,說明下層還需要滑動,上層不動offset為0 contentOffset.y = 0 } //不管怎麼樣,滑動即同步offset到上下文 syncScrollContext.innerOffset = contentOffset } } } }
第四步 兩個ScrollView巢狀,並正確設定下層scrollView的contentSize
在下層BottomScrollView裡面,新增topScrollView並設定contentSize。下層scrollView的contentSize的高 = headerView.height + topScrollView.height。這樣,當下層scrollView滑了y(y = headerView的高度)的時候,下層scrollView滑到底了,這時候c下層scrollView無法滑動,也就不存在手勢衝突,上層scrollView自動開始響應,流暢的滑動起來了
topScrollView.frame = CGRect(x: 0, y: offsetY, width: bounds.width, height: bounds.height) contentSize = CGSize(width: bounds.width, height: topScrollView.frame.maxY)
到這裡,就已經大功告成了!demo下載: Nested" target="_blank" rel="nofollow,noindex">https://github.com/wangdachui/ScrollViewNested
進階的需求
上下滑的同時,還要求左右滑:
具體就不多講了,有興趣看原始碼。 demo下載: https://github.com/wangdachui/TabScrollView