UIScrollView高級用法:delaysContentTouches與canCencelContentTouches屬性

需求:UIScrollView+btns。點擊btn高亮(Highlighted),滑動UIScrollView時取消高亮;研究了一下,整理如下:

1.實現:點擊btn高亮(Highlighted),滑動UIScrollView時保持高亮狀態,是不合理的,無法實現的。

2.實現:點擊btn高亮(Highlighted), 滑動UIScrollView時取消高亮狀態,是可以做到的。


按照從不同的要素去解釋原理:

一、UIScrollView原理,以時間爲軸線

從你的手指touch屏幕開始,scrollView開始一個timer,如果:

1.  150ms內如果你的手指沒有任何動作,消息就會傳給subView。

2.  150ms內手指有明顯的滑動(一個swipe動作),scrollView就會滾動,消息不會傳給subView。

3.  150ms內手指沒有滑動,scrollView將消息傳給subView,但是之後手指開始滑動,scrollView傳送touchesCancelled消息給subView,然後開始滾動。


二、UIScrollView原理,tracking屬性爲軸線

UIScrollView有一個BOOL類型的tracking屬性,用來返回用戶是否已經觸及內容並打算開始滾動,我們從這個屬性開始探究UIScrollView的工作原理:

當手指觸摸到UIScrollView內容的一瞬間,會產生下面的動作:

  • 攔截觸摸事件
  • tracking屬性變爲YES

  • 一個內置的計時器開始生效,用來監控在極短的事件間隔內是否發生了手指移動

    case1:當檢測到時間間隔內手指發生了移動,UIScrollView自己觸發滾動,tracking屬性變爲NO,手指觸摸下即使有(可以響應觸摸事件的)內部控件也不會再響應觸摸事件。

    case2:當檢測到時間間隔內手指沒有移動,tracking屬性保持YES,手指觸摸下如果有(可以響應觸摸事件的)內部控件,則將觸摸事件傳遞給控件進行處理。

     

當你手指是緩慢劃過或根本就沒動,纔會觸發UIButton的觸摸事件,這是case1的情況;

有很多新聞類的App頂部都有一個滑動菜單欄,主要模型可能是由一個UIScrollView包含多個UIButton控件組成;當你操作的時候,手指如果是很迅速的在上面劃過,會發現即使手指觸摸的地方有UIButton,但是並沒有觸發該UIButton的任何觸摸事件,這就是上面提到的case2。



上面的工作原理其實有一個屬性開關來控制:delaysContentTouches

官方解釋:

A Boolean value that determines whether the scroll view delays the handling of touch-down gestures.

中文翻譯:

一個布爾值,該值決定了滾動視圖是否延遲了觸控手勢的處理。

默認值爲YES;如果設置爲NO,則無論手指移動的多麼快,始終都會將觸摸事件傳遞給內部控件;設置爲NO可能會影響到UIScrollView的滾動功能。

delaysContentTouches的作用:

這個標誌默認是YES,使用上面的150ms的timer,如果設置爲NO,touch事件立即傳遞給subView,不會有150ms的等待。

Discussion

If the value of this property is YES, the scroll view delays handling the touch-down gesture until it can determine if scrolling is the intent. If the value is NO , the scroll view immediately calls touchesShouldBegin:withEvent:inContentView:. The default value is YES.

默認YES;如果設置爲NO,會馬上執行touchesShouldBegin:withEvent:inContentView:


- (BOOL)touchesShouldBegin:(NSSet<UITouch *> *)touches
                 withEvent:(UIEvent *)event 

inContentView:(UIView *)view

Discussion

The default behavior of UIScrollView is to invoke the UIResponder event-handling methods of the target subview that the touches occur in.

系統默認是允許UIScrollView,按照消息響應鏈向子視圖傳遞消息的。(即返回YES)

Return Value

Return NO if you don’t want the scroll view to send event messages to view. If you want view to receive those messages, return YES (the default).

如果你不想UIScrollView的子視圖接受消息,返回NO。

應用描述(作者註釋):這個方法是最先接收到滑動事件的(優先於button的

UIControlEventTouchDown,以及- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event),

如果返回YES,touche事件沿着消息響應鏈傳遞;

如果返回NO,表示UIScrollView接收這個滾動事件,不必沿着消息響應鏈傳遞了。



- (BOOL)touchesShouldCancelInContentView:(UIView *)view

應用描述(作者註釋):

如果返回YES:(系統默認)是允許UIScrollView,按照消息響應鏈向子視圖傳遞消息的

如果返回NO:UIScrollView,就接收不到滑動事件了。




再看另一個BOOL類型的屬性canCancelContentTouches,從字面上理解是“可以取消內容觸摸“,默認值爲YES。文檔裏的解釋是這樣的:

A Boolean value that controls whether touches in the content view always lead to tracking.

If the value of this property is YES and a view in the content has begun tracking a finger touching it, and if the user drags the finger enough to initiate a scroll, the view receives a touchesCancelled:withEvent: message and the scroll view handles the touch as a scroll. If the value of this property is NO, the scroll view does not scroll regardless of finger movement once the content view starts tracking.

翻譯爲中文大致如下:

這個BOOL類型的值控制content view裏的觸摸是否總能引發跟蹤(tracking)

cancelsTouches的作用:

應用描述(作者註釋):

 默認設置爲YES,

 如果設置爲NO,這消息一旦傳遞給subView,這scroll事件不會再發生。


如果屬性值爲YES並且跟蹤到手指正觸摸到一個內容控件,這時如果用戶拖動手指的距離足夠產生滾動,那麼內容控件將收到一個touchesCancelled:withEvent:消息,而scroll view將這次觸摸作爲滾動來處理。如果值爲NO,一旦content view開始跟蹤(tracking==YES),則無論手指是否移動,scrollView都不會滾動。

簡單通俗點說,如果爲YES,就會等待用戶下一步動作,如果用戶移動手指到一定距離,就會把這個操作作爲滾動來處理並開始滾動,同時發送一個touchesCancelled:withEvent:消息給內容控件,由控件自行處理。如果爲NO,就不會等待用戶下一步動作,並始終不會觸發scrollView的滾動了。



可以用一段代碼來驗證並觀察一下,定義一個MyScrollView繼承自UIScrollView,一個MyButton繼承自UIButton,然後重寫部分方法:

MyScrollView.m

複製代碼
- (BOOL)touchesShouldCancelInContentView:(UIView *)view
{
    [super touchesShouldCancelInContentView:view];
    
    NSLog(@"touchesShouldCancelInContentView");
    
    return YES;
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesCancelled:touches withEvent:event];

    
    NSLog(@"touchesCancelled");
}
複製代碼

MyButton.m

複製代碼
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesCancelled:touches withEvent:event];
    
    NSLog(@"【Button's touch cancelled】");
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesBegan:touches withEvent:event];
    
    NSLog(@"【Button's touch began】");
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesMoved:touches withEvent:event];
    
    NSLog(@"【Button's touch moved】");
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesEnded:touches withEvent:event];

    NSLog(@"【Button's touch ended】");
}
複製代碼

其實就是在各個方法執行時打印出一個標記,當canCencelContentTouches值爲YES時,用戶觸摸並移動手指再放開:

【Button's touch began

【Button's touch moved

  ……

【Button's touch moved

touchesShouldCancelInContentView

【Button's touch cancelled

當canCencelContentTouches值爲NO時,用戶觸摸並移動手指再放開:

【Button's touch began】

【Button's touch moved】

  ……

【Button's touch moved】

【Button's touch ended】


資料來源:
http://blog.sina.com.cn/s/blog_a3dbd02a0101749m.html
http://blog.csdn.net/opentogether/article/details/52185419


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章