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

UIScrollView嵌套的完美解决方案的更多相关文章

  1. WebBrowser脚本错误的完美解决方案

    原文:WebBrowser脚本错误的完美解决方案   当IE浏览器遇到脚本错误时浏览器,左下角会出现一个黄色图标,点击可以查看脚本错误的详细信息,并不会有弹出的错误信息框.当我们使用WebBrowse ...

  2. Apache服务器网站访问伪静态内页出现No input file specified.的完美解决方案

    原文地址:Apache服务器网站访问伪静态内页出现No input file specified.的完美解决方案 启用REWRITE的伪静态功能的时候,首页可以访问,而访问内页的时候,就提示:&quo ...

  3. Atitit.异常处理 嵌套  冗长的解决方案

    Atitit.异常处理 嵌套  冗长的解决方案 1. 异常处理的需要改进的地方1 2. +异常设计的初衷是, 在程序中出现错误时, 由程序自己处理错误, 尽量不要以exit(0)这种粗暴的方式中止程序 ...

  4. 关于Entity Framework中的Attached报错的完美解决方案终极版

    之前发表过一篇文章题为<关于Entity Framework中的Attached报错的完美解决方案>,那篇文章确实能解决单个实体在进行更新.删除时Attached的报错,注意我这里说的单个 ...

  5. No resource found that matches the given name 'Theme.AppCompat.Light 的完美解决方案

    No resource found that matches the given name 'Theme.AppCompat.Light 的完美解决方案 首先这个问题的产生是由于缺少Theme.App ...

  6. ecshop之transport和jquery冲突之完美解决方案

    众所周知:ecshop的transport.js文件和Jquery是冲突的,两个文件不能同时调用,现给出以下完美解决方案:原因分析:在transport.js文件中,大概 580行到590行之间,这个 ...

  7. Xcode6.1标准Framework静态库制作方法。工程转Framework,静态库加xib和图片。完美解决方案。

    http://www.cocoachina.com/bbs/read.php?tid-282490.html Xcode6.1标准Framework静态库制作方法.工程转Framework,静态库加x ...

  8. Safari 前端开发调试 iOS 完美解决方案

    转http://www.2cto.com/kf/201403/283404.html afari 前端开发调试 iOS 完美解决方案 2014-03-05      0个评论    来源:Safari ...

  9. C#多线程解决界面卡死问题的完美解决方案

    C#多线程解决界面卡死问题的完美解决方案 文章转自http://www.sufeinet.com/thread-3556-1-1.html 问题描述: 当我们的界面需要在程序运行中不断更新数据时, 当 ...

随机推荐

  1. Arch Linux中安装Anaconda

    安装步骤 通过AUR安装yaourt -S anaconda 激活Anaconda环境source /opt/anaconda/bin/activate root 关闭Anaconda环境source ...

  2. 对于Arrays的deep相关的方法。

    关于: deepEquals Arrays.equals(Object[] o1, Object[] o2):当是判断数组是引用类型数组的时候,从以下条件判断: 1.o1与o2指向同一个数组实例时,返 ...

  3. codeforces 540E"Infinite Inversions"

    传送门 题意: 给你一个无限大的整数序列  p = {1, 2, 3, ...}: 有 n 次操作,每次操作交换第 ai 个数和第 aj 个数: 求序列中逆序对的个数: 题解: 考虑交换完后的序列,存 ...

  4. selenium自动化测试原理和设计的分享

    昨天参加了公司的一个自动化测试的分享,有一些收获,记录一下. 1.主流的web端的UI自动化测试工具 基于浏览器API: selenium2.0,Watir(IE Driver) 基于JS 进行驱动: ...

  5. appium desktop 1.7 的swipe功能不能用,重写。

    rt // @Override public void swipe(int startx,int starty,int endx,int endy,int ms){ Duration duration ...

  6. 一个很适合初学者的selenium教程

    http://www.cnblogs.com/hustar0102/p/5885115.html

  7. python自动化开发-[第十四天]-javascript(续)

    今日概要: 1.数据类型 2.函数function 3.BOM 4.DOM 1.运算符 算术运算符: + - * / % ++ -- 比较运算符: > >= < <= != = ...

  8. 2018 CCPC-FINAL 后记

    赛前认为这将会是我生涯最接近铁牌的一场比赛,遗憾的是没有抓住机会,又打了个铜出来. 6题可做题,去掉签到4道数学题可把我无聊坏了,幸好主办方出了个算法题给我自娱自乐了几个小时. Day1热身赛,贪心, ...

  9. python kafka

    转载:https://zhuanlan.zhihu.com/p/31731892 https://kafka-python.readthedocs.io/en/master/usage.html

  10. 1042. Shuffling Machine (20)

    Shuffling is a procedure used to randomize a deck of playing cards. Because standard shuffling techn ...