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下載:https://github.com/wangdachui/ScrollViewNested
進階的需求
上下滑的同時,還要求左右滑:
具體就不多講了,有興趣看源碼。 demo下載:https://github.com/wangdachui/TabScrollView