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. 逆元(inv)

    当求解公式:(a/b)%m 时,因b可能会过大,会出现爆精度的情况,所以需变除法为乘法: 设c是b的逆元,则有b*c≡1(mod m): 则(a/b)%m = (a/b)*1%m = (a/b)*b* ...

  2. 译:Spring Boot 自动伸缩

    原文链接:https://dzone.com/articles/spring-boot-autoscaler 作者:Piotr Mińkowski 译者:helloworldtang 自动伸缩是每个人 ...

  3. nc 使用实例

    nc.exe -h即可看到各参数的使用方法.基本格式:nc [-options] hostname port[s] [ports] ...nc -l -p port [options] [hostna ...

  4. zabbix3.4 监控网卡流量设置差量

    自定义监控项的时候 看里面预设的监控网卡下载流量的key是 net.if.in[if,<mode>]这次可以监控流量了 但是添加监控项的时候 看网上别人的 都是可以设置数值是每秒的差量 出 ...

  5. System.Web.Optimization对脚本和样式表的压缩操作

    1 是否允许样式表压缩 BundleTable.EnableOptimizations = true; 在MVC项目中的 BundleConfig操作中是微软已经给我们准备好的CSS和JS压缩,我们可 ...

  6. python 二维数组读入

    study from : https://www.cnblogs.com/reaptomorrow-flydream/p/9613847.html python 二维数组键盘输入 1 m = int( ...

  7. spring 的 transactionManager 事务管理器 配置

    转: 事务的传播特<tx:advice id="txadvice" transaction-manager="transactionManager"> ...

  8. java键盘中输入q退出

    用 java.util.Scanner 如下 public static void main(String[] args) { while(true){ Scanner scan = new Scan ...

  9. HomeFragment 嵌套关系

    1.HomeFragment 在mainActivty 中调用: 2.HomeFragment 中: private ArrayList<Fragment> mFragments = ne ...

  10. Luogu P3868 [TJOI2009]猜数字

    题目链接 \(Click\) \(Here\) 中国剩余定理的板子.小心取模. #include <bits/stdc++.h> using namespace std; const in ...