需求:UIScrollView+btns篱瞎。點擊btn高亮(Highlighted)苟呐,滑動UIScrollView時取消高亮;研究了一下俐筋,整理如下:
1.實現(xiàn):點擊btn高亮(Highlighted)牵素,且滑動UIScrollView時保持高亮狀態(tài),是不合理的澄者,無法實現(xiàn)的笆呆。
2.實現(xiàn):點擊btn高亮(Highlighted)请琳,滑動UIScrollView時取消高亮狀態(tài),是可以做到的赠幕。
按照從不同的要素去解釋原理:
一俄精、UIScrollView原理,以時間為軸線:
從你的手指touch屏幕開始榕堰,scrollView開始一個timer嘀倒,如果:
1.150ms內(nèi)如果你的手指沒有任何動作,消息就會傳給subView局冰。
2.150ms內(nèi)手指有明顯的滑動(一個swipe動作),scrollView就會滾動灌危,消息不會傳給subView康二。
3. ?150ms內(nèi)手指沒有滑動,scrollView將消息傳給subView勇蝙,但是之后手指開始滑動沫勿,scrollView傳送touchesCancelled消息給subView,然后開始滾動味混。
二产雹、UIScrollView原理,以tracking屬性為軸線:
UIScrollView有一個BOOL類型的tracking屬性翁锡,用來返回用戶是否已經(jīng)觸及內(nèi)容并打算開始滾動,我們從這個屬性開始探究UIScrollView的工作原理:
當(dāng)手指觸摸到UIScrollView內(nèi)容的一瞬間,會產(chǎn)生下面的動作:
攔截觸摸事件
tracking屬性變?yōu)閅ES
一個內(nèi)置的計時器開始生效趟畏,用來監(jiān)控在極短的事件間隔內(nèi)是否發(fā)生了手指移動
case1:當(dāng)檢測到時間間隔內(nèi)手指發(fā)生了移動蕉扮,UIScrollView自己觸發(fā)滾動,tracking屬性變?yōu)镹O角溃,手指觸摸下即使有(可以響應(yīng)觸摸事件的)內(nèi)部控件也不會再響應(yīng)觸摸事件拷获。
case2:當(dāng)檢測到時間間隔內(nèi)手指沒有移動,tracking屬性保持YES减细,手指觸摸下如果有(可以響應(yīng)觸摸事件的)內(nèi)部控件匆瓜,則將觸摸事件傳遞給控件進(jìn)行處理。
當(dāng)你手指是緩慢劃過或根本就沒動未蝌,才會觸發(fā)UIButton的觸摸事件驮吱,這是case1的情況;
有很多新聞類的App頂部都有一個滑動菜單欄树埠,主要模型可能是由一個UIScrollView包含多個UIButton控件組成糠馆;當(dāng)你操作的時候,手指如果是很迅速的在上面劃過怎憋,會發(fā)現(xiàn)即使手指觸摸的地方有UIButton又碌,但是并沒有觸發(fā)該UIButton的任何觸摸事件九昧,這就是上面提到的case2。
上面的工作原理其實有一個屬性開關(guān)來控制:delaysContentTouches毕匀。
官方解釋:
A Boolean value that determines whether the scroll view delays the handling of touch-down gestures.
中文翻譯:
一個布爾值铸鹰,該值決定了滾動視圖是否延遲了觸控手勢的處理。
默認(rèn)值為YES皂岔;如果設(shè)置為NO蹋笼,則無論手指移動的多么快,始終都會將觸摸事件傳遞給內(nèi)部控件躁垛;設(shè)置為NO可能會影響到UIScrollView的滾動功能剖毯。
delaysContentTouches的作用:
這個標(biāo)志默認(rèn)是YES,使用上面的150ms的timer教馆,如果設(shè)置為NO逊谋,touch事件立即傳遞給subView,不會有150ms的等待土铺。
If the value of this property isYES, the scroll view delays handling the touch-down gesture until it can determine if scrolling is the intent. If the value isNO, the scroll view immediately callstouchesShouldBegin:withEvent:inContentView:. The default value isYES.
默認(rèn)YES胶滋;如果設(shè)置為NO,會馬上執(zhí)行touchesShouldBegin:withEvent:inContentView:
- (BOOL)touchesShouldBegin:(NSSet<UITouch*> *)toucheswithEvent:(UIEvent*)event
inContentView:(UIView*)view
The default behavior ofUIScrollViewis to invoke theUIResponderevent-handling methods of the target subview that the touches occur in.
系統(tǒng)默認(rèn)是允許UIScrollView悲敷,按照消息響應(yīng)鏈向子視圖傳遞消息的究恤。(即返回YES)
ReturnNOif you don’t want the scroll view to send event messages toview. If you wantviewto receive those messages, returnYES(the default).
如果你不想UIScrollView的子視圖接受消息,返回NO后德。
應(yīng)用描述(作者注釋):這個方法是最先接收到滑動事件的(優(yōu)先于button的
UIControlEventTouchDown部宿,以及- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event),
如果返回YES瓢湃,touche事件沿著消息響應(yīng)鏈傳遞;
如果返回NO窟赏,表示UIScrollView接收這個滾動事件,不必沿著消息響應(yīng)鏈傳遞了箱季。
- (BOOL)touchesShouldCancelInContentView:(UIView*)view
應(yīng)用描述(作者注釋):
如果返回YES:(系統(tǒng)默認(rèn))是允許UIScrollView涯穷,按照消息響應(yīng)鏈向子視圖傳遞消息的
如果返回NO:UIScrollView,就接收不到滑動事件了。
再看另一個BOOL類型的屬性canCancelContentTouches藏雏,從字面上理解是“可以取消內(nèi)容觸摸“拷况,默認(rèn)值為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里的觸摸是否總能引發(fā)跟蹤(tracking)
cancelsTouches的作用:
應(yīng)用描述(作者注釋):
默認(rèn)設(shè)置為YES掘殴,
如果設(shè)置為NO赚瘦,這消息一旦傳遞給subView,這scroll事件不會再發(fā)生奏寨。
如果屬性值為YES并且跟蹤到手指正觸摸到一個內(nèi)容控件起意,這時如果用戶拖動手指的距離足夠產(chǎn)生滾動,那么內(nèi)容控件將收到一個touchesCancelled:withEvent:消息病瞳,而scroll
view將這次觸摸作為滾動來處理揽咕。如果值為NO悲酷,一旦content
view開始跟蹤(tracking==YES),則無論手指是否移動亲善,scrollView都不會滾動设易。
簡單通俗點說,如果為YES蛹头,就會等待用戶下一步動作顿肺,如果用戶移動手指到一定距離,就會把這個操作作為滾動來處理并開始滾動渣蜗,同時發(fā)送一個touchesCancelled:withEvent:消息給內(nèi)容控件屠尊,由控件自行處理。如果為NO耕拷,就不會等待用戶下一步動作知染,并始終不會觸發(fā)scrollView的滾動了。
可以用一段代碼來驗證并觀察一下斑胜,定義一個MyScrollView繼承自UIScrollView,一個MyButton繼承自UIButton嫌吠,然后重寫部分方法:
MyScrollView.m
- (BOOL)touchesShouldCancelInContentView:(UIView *)view
{
[super touchesShouldCancelInContentView:view];
NSLog(@"touchesShouldCancelInContentView");returnYES;
}- (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】");
}
其實就是在各個方法執(zhí)行時打印出一個標(biāo)記止潘,當(dāng)canCencelContentTouches值為YES時,用戶觸摸并移動手指再放開:
【Button's touch began】
【Button's touch moved】
……
【Button's touch moved】
touchesShouldCancelInContentView
【Button's touch cancelled】
當(dāng)canCencelContentTouches值為NO時辫诅,用戶觸摸并移動手指再放開:
【Button's touch began】
【Button's touch moved】
……
【Button's touch moved】
【Button's touch ended】