关于 iOS 手势交互的一个问题,问了一圈居然无人能解,难道在 iOS 上实现这个手势交互是个无解的难题? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
iOS 开发实用技术导航
NSHipster 中文版
http://nshipster.cn/
cocos2d 开源 2D 游戏引擎
http://www.cocos2d-iphone.org/
CocoaPods
http://cocoapods.org/
Google Analytics for Mobile 统计解决方案
http://code.google.com/mobile/analytics/
WWDC
https://developer.apple.com/wwdc/
Design Guides and Resources
https://developer.apple.com/design/
Transcripts of WWDC sessions
http://asciiwwdc.com
Cocoa with Love
http://cocoawithlove.com/
Cocoa Dev Central
http://cocoadevcentral.com/
NSHipster
http://nshipster.com/
Style Guides
Google Objective-C Style Guide
NYTimes Objective-C Style Guide
Useful Tools and Services
Charles Web Debugging Proxy
Smore
forkon
V2EX    iDev

关于 iOS 手势交互的一个问题,问了一圈居然无人能解,难道在 iOS 上实现这个手势交互是个无解的难题?

  •  
  •   forkon 2018-04-19 08:28:19 +08:00 12477 次点击
    这是一个创建于 2735 天前的主题,其中的信息可能已经有所发展或是发生改变。

    [前提] scrollViewA 和 scrollViewB 在同一个父视图里面,不嵌套,且 scrollViewA 在 scrollViewB 的之上。

    [要实现的交互] 手指先是在 scrollViewA 里面向上滑动,当 scrollViewA 滚动到最底下时,scrollViewB 接着滚动,这个过程中手指从未抬起(相当于 touch 事件从 scrollViewA 传到了 scrollViewB ?)。

    +-----------------------------------+ | | | | | | | | | | ^ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | scrollViewA | | | | | + | | | | | | | +---------------------------+ | | | | | | | | scrollViewB | | | | | +-----------------------------------+

    第 1 条附言    2018-04-19 10:43:33 +08:00
    感谢大家的热心回复,让我大大开了脑洞,谢谢。对于这个问题,再做一点补充吧:

    1. 位于上面的 scrollViewA 的高度会随着 scrollViewB 的滚动而改变。
    2. scrollViewA 和 scrollViewB 的还要接收其它 touch 事件,所以 userInteractionEnabled 不能为 false。
    3. 由于某些原因这里不能只用一个 scrollView。
    第 2 条附言    2018-04-19 11:12:56 +08:00
    经 V 友提醒,补充一下 scrollViewA 和 scrollViewB 的 frame 的情况:

    scrollViewB 完全覆盖其 superView (宽高一样),scrollViewA 与 scrollViewB 同宽且左上角对齐,但 scrollViewA 的高度始终要比 scrollViewB 小。
    60 条回复    2018-04-20 19:00:44 +08:00
    whileFalse
        1
    whileFalse  
       2018-04-19 08:59:48 +08:00
    不懂 iOS,但曾经是个前端。
    scrollViewA 能不能捕获滑动事件?捕获之后发现自己滚到头了,就向 scrollViewB 派发滑动事件即可。
    sea516
        2
    sea516  
       2018-04-19 09:08:06 +08:00
    初学者吧
    forkon
        3
    forkon  
    OP
       2018-04-19 09:16:35 +08:00
    @sea516 莫非你知道答案,请不吝赐教。

    @whileFalse 没那么简单。
    mcluyu
        4
    mcluyu  
       2018-04-19 09:27:09 +08:00
    1.A 在上 B 在下, 向上滑怎么还会滑到 B ?我理解有问题?要不你画个图表示一下。。
    2.你手放在 A 上滑不抬起,触摸事件怎么会自己跑到 B 上面呢,区分一个触摸事件的触摸范围自然是从第一次触摸时算起,要是范围在不抬起(不改变初始触摸范围)的情况下自己变了才是有问题吧?比如不小心滑动到一个按钮上面,那是不是要算点击了该按钮呢?
    3.为什么会有这么奇怪的需求,A 滑动到底部时 B 继续,那用一个 ScrollView 不就行了吗?
    3a3Mp112
        5
    3a3Mp112  
       2018-04-19 09:29:34 +08:00
    scrollViewA 生成 delegate, 当 B 到达底部的时候, 给 A 的代理发消息,做你想要做的动作。
    elgae
        6
    elgae  
       2018-04-19 09:31:26 +08:00 via iPhone
    可以做的,有个关于手势的文档,读了就明白了
    elgae
        7
    elgae  
       2018-04-19 09:34:57 +08:00 via iPhone
    @elgae 我错了
    fadaixiaohai
        8
    fadaixiaohai  
       2018-04-19 09:35:02 +08:00
    看不懂,向上滑还会滑到屏幕最底下?什么操作?
    你最好理解一下滑动的本质是什么?了解一下怎么用 layout 来做视图的滑动。
    C90
        9
    C90  
       2018-04-19 09:37:50 +08:00
    hitTest 了解一下
    sunhr
        10
    sunhr  
       2018-04-19 09:40:21 +08:00
    你可以把两个 scrollView 的 scrollEnabled 都禁掉,然后在 superview 上加一个 UIPanGestureRecognizer,自己通过这个手势去计算设置两个 scrollView 的 contentOffset
    itenyh
        11
    itenyh  
       2018-04-19 09:43:10 +08:00
    @forkon 我试了一下,基本实现了你的需求,等下空了发上来,不过有点绕。感觉 @sea516 有更简单的办法。
    zvving
        12
    zvving  
       2018-04-19 09:44:06 +08:00
    拆分的目的是什么,想达到什么效果?为什么不用一个 scroll view 解决?
    dcty
        13
    dcty  
       2018-04-19 09:47:56 +08:00
    UITableview
    >CellOne ( firstScrollView )
    >CellTwo ( secondScrollview )

    cellHeight equalTo Scrollview ContentHeight

    这个是能满足需求的,并且从扩展性和性能上来说,也不至于有特别大的问题

    如果说你担心的是复用问题,或者懒加载问题,那么则应该想办法用 TableView 或者 collectionview 来解决
    另外多个 Scrollview 嵌套是官方不推荐的,当然我们也实现过,能实现,从技术的角度出发,实现有点戳,并且不是特别优,从用户体验的角度出发,没毛病。

    楼上的人也有提到过,你这个需求如果不是为了扩展性,一个 Scrollview 就解决了啊。
    forkon
        14
    forkon  
    OP
       2018-04-19 09:50:00 +08:00
    @mcluyu 很用心的回复,多谢……需求就是这么奇葩,而且还不能只用一个 ScrollView。
    @3a3Mp112 可以通过这种方法改变 B 的 contentOffset,但手指抬起之后 B 没有一个减速停止的过程。
    @elgae 哪个文档
    tigerZhang
        15
    tigerZhang  
       2018-04-19 09:53:17 +08:00
    同意楼上的观点。
    itenyh
        16
    itenyh  
       2018-04-19 09:55:18 +08:00
    懒得写了,简单说一下核心思想:和 @sunhr 实质是一样的。 首先 superview 是一个 scrollview (简称 C ),superview 上摆放你的 scrollviewA ( A ), scrollviewB ( B ):
    1. AB 把 C 完全遮住
    2. AB userInteractiOnEnabled= NO
    3. C 的 contentsize 的 height (如果你是垂直滑动)等于 AB 的 contentsize 的 height 之和。
    好了,现在你滑动手势并不会让 AB 移动,而是让 C 移动,不过你看不到。这样,你可以通过 C 的 contentoffset 的变化来操纵 AB 的 contentoffset 的变化了。
    核心思路是这样,还有些细节,慢慢搞吧。
    sunhr
        17
    sunhr  
       2018-04-19 10:02:05 +08:00
    @itenyh

    兄弟,你这个肯定是不行的~

    1、如果 C 是 scrollView 的话,AB 会跟着滚动的

    2、userInteractiOnEnabled= NO 的话,如果 AB 需要接受 touch 事件就杯具了呐,应该是 scrollEnabled = NO
    itenyh
        18
    itenyh  
       2018-04-19 10:05:23 +08:00
    @sunhr 第一点经我试验,AB 肯定是不会跟随的。 第二点如果 AB 需要接受 touch 事件就杯具了,这特么确实是个悲剧!看看有没有办法改进。
    snail1988
        19
    snail1988  
       2018-04-19 10:08:19 +08:00
    @forkon 两个方案都很简单 1 上面 scrollview 放在下面大 scrollview 上,自己实现 UIGestureRecognizerDelegate 允许两个 scrollview 同时相应手势,然后家逻辑判断哪个 scrollview 不能动即可
    snail1988
        20
    snail1988  
       2018-04-19 10:09:33 +08:00
    @forkon 第二个方案 上层覆盖透明 scrollview 把 offset 实时同步给下面两个 scrollview
    建议第一个 很简单 自己实现 pan 手势代理即可
    elgae
        22
    elgae  
       2018-04-19 10:11:49 +08:00
    @C90 强行 hitTest
    forkon
        23
    forkon  
    OP
       2018-04-19 10:12:11 +08:00
    @sunhr 我现在用的就是类似这种方法,但是效果不理想,手指抬起之后,scrollView 没有一个减速停止的过程。

    @zvving 设计上是在上面的 scrollView 的背景色是半透明的,当点击下面的 scrollView 上的内容时,上面的 scrollView 作相应的显示。

    @dcty 达不到设计的要求。

    @itenyh 感谢,我也小激动了一把,不过,A 和 B 的 userInteractionEnabled 都不能为 NO,还有其它 touch 事件要处理。
    amon
        24
    amon  
       2018-04-19 10:12:29 +08:00
    这样的交互简直是爆炸。
    建议弄成一个 ScrollView,或者 TableView。
    forkon
        25
    forkon  
    OP
       2018-04-19 10:22:51 +08:00
    @elgae
    @C90

    hitTest 也试过了,hitTest 只有在手指按下的瞬间起作用,按下之后在滑动的过程中系统就不会对其它 view 做 hitTest 了。
    loveuqian
        26
    loveuqian  
       2018-04-19 10:29:13 +08:00
    为啥要这么做?
    这不就是一个 table2 个 cell ?
    ichanne
        27
    ichanne  
       2018-04-19 10:38:02 +08:00
    什么垃圾需求才需要这样设计,说吧,抄的哪个 App,这样问题更好解决
    learnshare
        28
    learnshare  
       2018-04-19 10:42:41 +08:00
    我感觉这个交互不是很自然,一个页面中可滚动区域应该最多只有一个
    forkon
        29
    forkon  
    OP
       2018-04-19 10:46:16 +08:00
    @ichanne
    @learnshare

    从设计师的描述来看这样的效果还是蛮不错的,哈哈。
    ichanne
        30
    ichanne  
       2018-04-19 10:47:40 +08:00
    @forkon #29 直接问设计师抄的哪个 App
    cnbobolee
        31
    cnbobolee  
       2018-04-19 10:49:15 +08:00
    按你说的原理上是不行的,上面滑动到底,手指不离开事件不会转移到下面的 scrollView,但是实现你这个需求可以用其他方法,比如上面 scrollView 和下面 scrollView 进行通信。
    sunhr
        32
    sunhr  
       2018-04-19 10:52:30 +08:00
    @forkon 要减速效果,自己根据 pan 的速度搞一下就好啦,很简单的
    forkon
        33
    forkon  
    OP
       2018-04-19 10:53:56 +08:00
    @snail1988

    方案 1:不是很明白,scrollViewA 滑到底就滑不动了,还能让 scrollViewB 继续滑动?加 pan 手势改变 offset 的话,手指抬起没有减速停下来的效果。
    方案 2:覆盖透明 scrollView,那下来的两个 scrollView 怎么接收其它的 touch 事件?
    LeoNG
        34
    LeoNG  
       2018-04-19 11:02:12 +08:00
    你没有说两个 scrollview 的 frame。纵向平分 super view ?
    forkon
        35
    forkon  
    OP
       2018-04-19 11:05:44 +08:00
    @cnbobolee 我在想能不能在上面的滑到底之后,把当前的 touch 事件取消掉,上面的 scrollView 的 userInteractionEnabled 临时置为 false,手指继续滑动,系统生成新的 touch 事件,此时下面的 scrollView 捕获事件……经测试,手指不抬起就不会有新的 touch 事件产生……
    dcty
        36
    dcty  
       2018-04-19 11:08:53 +08:00
    难道是第一个 Scrollview 需要做视觉差的效果?
    如果说,我说的第一个方案完全满足需求,不服来辩。
    forkon
        37
    forkon  
    OP
       2018-04-19 11:14:57 +08:00
    @LeoNG 谢提醒,已补充:scrollViewB 完全覆盖其 superView (宽高一样),scrollViewA 与 scrollViewB 同宽且左上角对齐,但 scrollViewA 的高度始终要比 scrollViewB 小。

    @dcty 不是视差的效果,最多可以说是联动吧。
    itenyh
        38
    itenyh  
       2018-04-19 11:15:05 +08:00   1
    @forkon 继续谈谈 ABC 三个 scrollview 这样的架构。刚才的 16 楼的方案,有 userInteractionEnabled 必须设置为 NO 的致命问题,这其实是彻底放弃了 AB 对任何手势的响应权利。但是这个方案明显的优点在于利用 C 的手势,解决 AB 滑动以及其流畅切换的问题。
    那么思考一下,我能不能在 scroll 的时候让 C 来响应,而 touch 让 AB 来响应。从更底层来思考一下,当手指触碰到屏幕并产生动作后,系统应该要决定两件事:1、谁来响应(通过 hittest ) 2、如何响应( scroll,touch......)。能不能在知道是 scroll 后让 C 来响应,touch 则 redirect 给 AB ?
    kera0a
        39
    kera0a  
       2018-04-19 11:19:40 +08:00
    应该可以通过手势代理方法 shouldRecognizeSimultaneously,让两个 ScrollView 同时接受滑动手势,之后根据状态两个 ScrollView 做不同的处理(不用让手势停止,只需重载 ScrollView 获取手势事件做相应操作, 方法之后继续处理)

    难点是让两个 ScrollView 同时接受手势, 但嵌套 ScrollView 的教程这么多,你多搜搜就行,我吧吧这么多可不想写测试代码。
    forkon
        40
    forkon  
    OP
       2018-04-19 11:28:23 +08:00
    @itenyh 很好的思路,但是似乎并不好解决,难点是怎么 touch redirect 给其它 view。

    @kera0a 是我没找着吗,我看到的都是同步 contentOffset,显然这种做法在这里用不上。
    dreamCatcher
        41
    dreamCatcher  
       2018-04-19 12:25:06 +08:00
    问题很复杂很怪,往往是问题问错了
    肯定有其他办法解决源头问题
    cnbobolee
        42
    cnbobolee  
       2018-04-19 12:40:18 +08:00
    @forkon 最好不要这么干,首先手势不产生行为的话是没有事件触发的(这个哪个平台都是这样设计的),还有就是你说的临时取消之类的操作,这个操作理论上是可以的,实际操作后不会随时变化的,环境太复杂。
    snail1988
        43
    snail1988  
       2018-04-19 12:49:59 +08:00   1
    @forkon 我说的方案 1 有个人写了个 demo 你看看是不是能达到你要的效果
    https://github.com/indulgeIn/YBMultistageScrollView
    forkon
        44
    forkon  
    OP
       2018-04-19 13:10:07 +08:00
    @snail1988 虽然和我要的效果有比较大的出入,但有一定的研究价值。
    nathanw
        46
    nathanw  
       2018-04-19 13:23:36 +08:00   1
    我来终结此贴吧。

    https://github.com/maxep/MXParallaxHeader
    看主要看第三个 demo.

    解决方法:
    B 放在 A 里面,A 的 contentInset 的 bottom 设置大一点。
    设置 A 和 B 一起滑动,但通过 KVO 判断,使得其中一个滑动被还原,即没产生滑动效果。
    具体参考代码。
    我做了 2 个 tableView 的嵌套,用这个库改了下自己用。

    hittest 没有用,
    1.衔接的部分如果滑动未停止,手势的对象是无法突然改变。
    2.如果 scroll view C 并列放 A 和 B,这样 A 和 B 有其他点击操作会出问题。
    nathanw
        47
    nathanw  
       2018-04-19 13:35:26 +08:00
    补充楼上:
    1. 加 UIPanGestureRecognizer,滑动效果很差,和 scollview 的不一样.
    2. hittest 时还不知道是 pan 还是 tap,所以无法决定手势传给谁. a b 并列放不可行.
    nathanw
        48
    nathanw  
       2018-04-19 13:58:30 +08:00
    还有,代码里面有个 cancelsTouchesInView 最好改为 YES
    spongebobsun
        49
    spongebobsun  
       2018-04-19 16:59:46 +08:00
    这问题感觉像上个月碰到的一个面试题。。。
    expkzb
        50
    expkzb  
       2018-04-19 17:56:31 +08:00
    军儿
    forkon
        51
    forkon  
    OP
       2018-04-19 19:08:22 +08:00
    @expkzb 哪哪都能碰到你 哈哈
    forkon
        52
    forkon  
    OP
       2018-04-19 19:09:57 +08:00
    @nathanw 稍微有点遗憾 还终结不了
    w99wen
        53
    w99wen  
       2018-04-19 20:10:40 +08:00
    baseview 添加 pan,scroll1 的 offset 加 kvo,scroll 到底,scroll1 userinteraction = NO,pan 接收 gesture,控制 scroll2 的 offset。
    wezzard
        54
    wezzard  
       2018-04-20 02:16:10 +08:00
    很容易,多看看 WWDC
    ybh37
        55
    ybh37  
       2018-04-20 07:55:32 +08:00
    事件捕获、手势事件穿透、底层 View 动画控制
    不难啊
    不清楚这种交互为什么被描述的这么复杂?
    kitalphaj
        56
    kitalphaj  
       2018-04-20 08:07:26 +08:00
    哈哈,以后 iDev 的发帖都请参考这个标题,不然这些大佬不会出来回复的哈哈哈哈哈
    wsj195328
        57
    wsj195328  
       2018-04-20 09:02:27 +08:00
    手势代理判断当前 view
    C90
        58
    C90  
       2018-04-20 09:03:22 +08:00
    @forkon 确实 hittest 无法发挥作用
    @ybh37 大神能否出个 demo ?
    HelveticaNeue
        59
    HelveticaNeue  
       2018-04-20 13:27:37 +08:00
    @ichanne 应该是抄的 Twitter 个人页
    我做过一个差不多的。挺麻烦的。我是抄的微博。Twitter 怎么实现的看不出来
    楼主用 Reveal 看一下微博的个人页应该就明白了
    ostholz
        60
    ostholz  
       2018-04-20 19:00:44 +08:00
    个人感觉有点困难, 我们的 App 也涉及到这样的问题, 比你这个还要复杂. 至今也没有好解决办法.
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2759 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 35ms UTC 15:07 PVG 23:07 LAX 08:07 JFK 11:07
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86