雖然蘋果建議咱們不要嵌套 UIScrollview ,可是這話究竟是對開發者說的,架不住設計師就是要這麼畫設計圖。
我遇到的場景相似這樣,頂部有一個優先展現的封面,當封面滾出屏幕後下面內容的 ScrollView 開始滾動。由於介紹、目錄、評價三個tab是能夠左右滾動的,因此具體內容是嵌套在一個 ScrollView 裏的。swift
可是其實面臨的問題都是類似的,嵌套了 ScrollView 後,須要一個條件來控制某個具體時刻哪一個 ScrollView 響應滑動事件。xcode
第一步就是經過設置 ScrollView 的 isScrollEnabled 屬性來控制哪一個響應。ui
@IBOutlet weak var mainScrollView: UIScrollView!
@IBOutlet weak var subScrollView: UIScrollView!
let maxOffsetY: CGFloat = 150
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView == mainScrollView {
if scrollView.contentOffset.y >= maxOffsetY {
mainScrollView.isScrollEnabled = false
subScrollView.isScrollEnabled = true
}
}else {
// 當 subScrollView 滑動到頂時,中止響應,mainScrollView 開始響應。
if scrollView.contentOffset.y <= 0 {
subScrollView.isScrollEnabled = false
mainScrollView.isScrollEnabled = true
}
}
}複製代碼
在代理方法 scrollViewDidScroll
中進行判斷,當mainScrollView 劃出 150 後,中止響應滾動,這時手指再滑動,天然就是展現內容的 subScrollView 響應了。spa
可是這個簡單的方案在實際操做中有一個很大的弊端。用戶並不老是完美的在滑動了 150 pt後,停下手。而後再開始進行下一次的滑動。若是用戶一次性滾了 160 ,mainScrollView 就會在滾動到 150 後中止響應,此時由於是一次手勢, subScrollView 也不會響應。用戶就會有被中斷的感受,須要第二次滑動才能繼續滾動。翻譯
因此這種簡單的粗暴的方案不能解決問題。畢竟用戶是咱們爸爸。設計
你們都知道每一個 ScrollView 都帶了一個 panGestureRecognizer 。此時,咱們開始幻想,要是能夠本身控制這個 pan 手勢就行了。
因而咱們寫下了這行的代碼開始進行嘗試:代理
mainScrollView.panGestureRecognizer.delegate = self複製代碼
你們都知道 iOS 的開發體驗特別好,蘋果的 API 都特別爲你們着想。程序 run 起來後 xcode 就會給你一個 crash:code
'UIScrollView's built-in pan gesture recognizer must have its scroll view as its delegate.'cdn
我來翻譯一下:
blog
解決的思路是咱們本身添加一個聯動的 pan 手勢。注意在UIGestureRecognizerDelegate
中的shouldRecognizeSimultaneously
返回 true。
let pan = UIPanGestureRecognizer(target: self, action: #selector(ViewController.panGestureRecognizerAction(recognizer:)))
pan.delegate = self
mainScrollView.addGestureRecognizer(pan)
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}複製代碼
這樣設置以後就能夠在不影響滾動的狀況下獲取 pan 手勢的參數。
接着咱們定義兩個 bool 值判斷當前滾動的 ScrollView
var mainScrollEnabled = false
var subScrollEnabled = false複製代碼
在 scrollViewDidScroll 中設置:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView == mainScrollView {
if scrollView.contentOffset.y >= maxOffsetY {
scrollView.setContentOffset(CGPoint(x: 0, y: maxOffsetY), animated: false)
mainScrollView.isScrollEnabled = false
subScrollView.isScrollEnabled = true
subScrollEnabled = true
}
}else {
if scrollView.contentOffset.y <= 0 {
scrollView.setContentOffset(CGPoint(x: 0, y: 0), animated: false)
subScrollView.isScrollEnabled = false
mainScrollView.isScrollEnabled = true
mainScrollEnabled = true
}
}
}複製代碼
接着咱們在 pan 的手勢的 recognizer 中響應處理這種滑動通過臨界區域的狀況:
var currentPanY: CGFloat = 0
func panGestureRecognizerAction(recognizer: UIPanGestureRecognizer) {
if recognizer.state != .changed{
currentPanY = 0
// 每次滑動結束都清空狀態
mainScrollEnabled = false
subScrollEnabled = false
}else {
let currentY = recognizer.translation(in: mainScrollView).y
// 說明在此次滑動過程當中通過了臨界點
if mainScrollEnabled || subScrollEnabled {
if currentPanY == 0 {
currentPanY = currentY //記錄下通過臨界點是的 y
}
let offsetY = currentPanY - currentY //計算在臨界點後的 offsetY
if mainScrollEnabled {
let supposeY = maxOffsetY + offsetY
if supposeY >= 0 {
mainScrollView.contentOffset = CGPoint(x: 0, y: supposeY)
}else {
mainScrollView.contentOffset = CGPoint.zero
}
}else {
subScrollView.contentOffset = CGPoint(x: 0, y: offsetY)
}
}
}
}複製代碼
這種方案的缺點就是通過臨界點的運動曲線會不天然。原生的滑動有個仿物理的彈性,而咱們是直接設置 contentOffset 獲得的,是線性的。
歡迎關注個人微博:@沒故事的卓同窗