ScrollView內嵌SubView時手勢處理原理

在實現上一篇介紹的自定義滑動關聯菜單控件BFScrollMenu時,關于滑動方向判斷的邏輯其實一開始是準備用手勢操作來實現的,結果發現在ScrollView中處理手勢的邏輯比較困難,在寫的時候還沒有仔細研究過ScrollView的滑動原理,只是知道需要自定義一個ScrollView才能實現,但是我的本意是不希望用戶還要顯示指定一個自定義的ScrollView而是直接用category進行無縫的對接,所以就改用didScroll delegate 用offset來計算滑動邏輯了,效果可以接受。

回過頭來花了些時間仔細研究了下UIScrollView的滑動處理邏輯,這樣就可以采用Customer ScrollView來實現同樣的BFScrollMenu邏輯了。

  • UIScrollView的滑動處理原理

網上相關的文章有很多,但是我感覺沒有一個能清晰的解釋清楚。這里我用流程圖的方式,把我自己經過試驗后的結論和理解和大家分享一下,希望能幫助到你。如果有不正確的歡迎指正。

首先我們來看Apple的官方代碼文檔里的注釋:

Scrolling with no scroll bars is a bit complex. on touch down, we don't know if the user will want to scroll or track a subview like a control. on touch down, we start a timer and also look at any movement. if the time elapses without sufficient change in position, we start sending events to the hit view in the content subview. if the user then drags far enough, we switch back to dragging and cancel any tracking in the subview. the methods below are called by the scroll view and give subclasses override points to add in custom behaviour. you can remove the delay in delivery of touchesBegan:withEvent: to subviews by setting delaysContentTouches to NO.

然后再往下看2個可以set的property和2個可以重載的方法:

// default is YES. if NO, we immediately call -touchesShouldBegin:withEvent:inContentView:. 
// this has no effect on presses
@property(nonatomic) BOOL delaysContentTouches;
// default is YES. if NO, then once we start tracking, we don't try to drag if the touch moves. 
// this has no effect on presses   
@property(nonatomic) BOOL canCancelContentTouches;

// override points for subclasses to control delivery of touch events to subviews of the scroll view
// called before touches are delivered to a subview of the scroll view. 
// if it returns NO the touches will not be delivered to the subview
// this has no effect on presses
// default returns YES
- (BOOL)touchesShouldBegin:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event inContentView:(UIView *)view;

// called before scrolling begins if touches have already been delivered to a subview of the scroll view. 
// if it returns NO the touches will continue to be delivered to the subview and scrolling will not occur
// not called if canCancelContentTouches is NO. default returns YES if view isn't a UIControl.
// this has no effect on presses
- (BOOL)touchesShouldCancelInContentView:(UIView *)view;

有了以上這些,已經基本能夠理解ScrollView對手勢操作的處理原理,這里來統一歸納一下。我們直接上圖最清晰:

scrollView.png

解釋下上圖:

  1. Apple使用了一個延遲機制來判斷在一個ScrollView內是否產生有效的滑動手勢
    ,而這個延遲機制由開關delaysContentTouches來控制,默認為YES
  2. 如果延遲打開且檢測到有效Scroll,則將會直接發送滑動操作到ScrollView,并停止向SubView發送任何tracking;
  3. 如果延遲未打開或者打開但是沒有檢測到有效的動作,則會看touchesShouldBegin:withEvent:inContentView:的返回:NO則立即返回給ScrollView,否則會將touch事件發送給SubView。默認為YES。
    當touch事件已經發送給SubView之后,如果用戶繼續產生touch事件(做出Scroll動作),則:
  4. 檢查canCancelContentTouches開關(默認為YES)。如果為N,則將所有后續事件發送到SubView,否則:
  5. 檢查touchesShouldCancelInContentView的返回值,如果為N,則將所有事件發送到SubView,否則返回到ScrollView。默認情況下,如果SubView不是UIControl的一種,則返回YES。
  • 一個簡單的總結:

針對1,2: 默認情況下,只要用戶迅速做出滑動手勢,都將觸發ScrollView滑動;
針對3:默認情況下,點擊操作都可以傳入到SubView
針對4,5:默認情況下,SubView中的Button, UISlider, UISwitch等等都可以直接響應你的Touch,滑動事件;但是UIView之類則無法響應復雜事件(Multi-Touch)

  • 想要讓SubView響應手勢操作 ?

最簡單的:

  1. 關閉delaysContentTouches
  2. 關閉canCancelContentTouches
    如果想要再多一些自定義,比如有些地方響應,有些地方不響應,則可以:
    touchesShouldBegin:withEvent:inContentView:中設定響應條件,或者:
    打開canCancelContentTouches,在 touchesShouldCancelInContentView:中設定響應條件。
  • Sample Demo

說這么多還是沒懂?再來一發Demo,直接看代碼最清晰:
這個Demo中,首先自定義一個ScrollView叫做“MyScollView”,在MyScollView上,有2個SubView:綠色的greenView和黃色的yellowView,兩個View都添加的左右滑動的手勢操作,但是在touchesShouldBegin:withEvent:inContentView:方法中,當檢測到當前view為greenView時,返回NO。
另外,有2個Switch開關,分別操作delaysContentTouchescanCancelContentTouches

Demo - Github地址

我們可以看一下效果:
1) 初始狀態下,所有開關打開,UIButton正常工作,yellowView不能響應手勢;
a) 觸動yellowView屏幕后馬上滑動,則UISilder不能正常工作,ScrollView滑動;
b) 觸動yellowView屏幕后等待一小會再滑動,則UISilder正常工作;

ScrollView.gif

2) 關閉canCancelContentTouches
a) 觸動yellowView屏幕后馬上滑動,則ScrollView滑動;
b) 觸動yellowView屏幕后等待一小會再滑動,則UISilder, yellowView響應手勢;

ScrollView2.gif

3)關閉delaysContentTouches,無論怎樣,UISilder,yellowView都會響應手勢;

ScrollView3.gif

4)以上任何情況,greenView始終不會響應手勢

希望你喜歡,歡迎大家討論。

2016.6.14 完稿于南京

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,527評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,687評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,640評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,957評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,682評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,011評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,009評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,183評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,714評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,435評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,665評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,148評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,838評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,251評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,588評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,379評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,627評論 2 380

推薦閱讀更多精彩內容