虽然苹果建议咱们不要嵌套 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 获得的,是线性的。
欢迎关注个人微博:@没故事的卓同窗