WKWebView 中的手勢識別

Mondo 科技 更新 2024-03-03

本文重點介紹了 WebKit 原始碼中 wkwebview 支援的複雜手勢處理邏輯,並研究了手勢處理在 iOS 系統中的高階用法。

在 iOS 中,螢幕點選事件從頭到尾會經歷以下步驟[1]

使用者點選螢幕生成乙個硬體觸控事件,作業系統將硬體觸控事件包裝成 iohidevent 併發送到 Springboard,Springboard 再將 iohidevent 傳送給當前開啟的 app 程序,app 程序接收到該事件後,喚醒主線程 runloop(source1),並在 source1 中觸發 source0 將 iohidevent 包裝在 uiEvent 物件中, 傳送到頂級 UIAwindow(-[UIInit sendevent:]) UIAwindow 呼叫 HitTest 方法查詢 UITavice 中每個 UITuTouch 例項的 HitTestView,並記錄最終的 HitTestView 和掛載在 HitTest 遞迴呼叫程序上的每個父檢視 同一級別的多個檢視之間的 GestureRecognizer(記錄為 GestureRecognizers 屬性)以相反的順序呼叫(在 AddSubview 之後, 首先呼叫 HitTest),預設情況下,根據 PointInside 方法的返回值,它決定是否以遞迴方式查詢其子檢視,實現中的注意事項:

手動呼叫 hittest:withevent: 方法時,需要遞迴地對子檢視進行 hittest 測試,方法是將坐標點轉向目標檢視的坐標系並呼叫 [super hittest:withevent:](預設邏輯)。

wkcontentView- (UIVet *)hitTest:(CGPorent)Point WithEvent:(UITature *)Event } 預設 HitTest 邏輯,遞迴遍歷子檢視 UIVerview* HitView = [Super HitTest:Point withEvent:Event:Event]; return hitview;}
通過過載 hittest:withevent: 方法,並在其中定義要直接測試或返回的特定檢視。

需要注意的是,以下例程中沒有呼叫 [super hittest:withevent:],即不遍歷子檢視做 hittest 的預設邏輯在 ** 中的注釋中有詳細說明

wkCompositin**iew- (UIVet *)HitTest:(CGPorent)Point WithEvent:(UITert) Event:(UITert) 具體實現為: uiView(wkhitTesting)- UIView *)Web FindDescendantViewAtPoint:(CGPorent)Point WithEvent:(UITat) Event 直接返回到這樣的檢視,而不是遞迴地查詢子檢視 if ([view iskindofclass:[wkchildscrollview class]]) ditto if ([view iskindofclass:webkit::scrollviewscrollindicatorclass()]view.superview iskindofclass:wkchildscrollview.class]) //ignoring other views } return nil;}
CSS 中的 touch-action 屬性用於設定觸控螢幕使用者如何操作元素的區域,並具有以下值(有關詳細資訊,請參見 [2]):

/* keyword values */ touch-action: auto; touch-action: none; touch-action: pan-x; touch-action: pan-left; touch-action: pan-right; touch-action: pan-y; touch-action: pan-up; touch-action: pan-down; touch-action: pinch-zoom; touch-action: manipulation; /* global values */ touch-action: inherit; touch-action: initial; touch-action: unset;
例如,如果 DOM 元素的 touch-action 屬性設定為 None,則 WebView 不允許使用觸控手勢輕掃該元素。

實現方案(詳見**片段和注釋):

為了支援觸控操作,在 webkit 中定義了乙個特殊的 wktouchactiongesturerecognizer,並將其作為最後乙個手勢識別器新增到 wkcontentview(請參閱第一部分中提到的事件排程優先順序,最後乙個手勢識別器)。在 wkTouchActionGestureRecognizer 的 TouchesBegin、Touchesmoved 和 TouchesEnded 方法的實現中,直接呼叫 updateState 將手勢識別狀態設定為識別成功。這樣,按照第一部分提到的處理手勢衝突的邏輯,其他手勢識別器和 hittestviews 會接收到觸控取消**,以達到阻止其他手勢響應的效果注意:不是所有的手勢都被阻止,哪些手勢不能被阻止,還依賴於以下解決手勢衝突的方法來實現 wkTouchActionGestureRecognizer 的 CanBePreventedByGestureRecognizer: 該方法返回 no,這意味著即使另乙個手勢識別器已被識別為成功,它仍然可以識別成功並繼續接收觸控 訊息 WKTicACTIONTegACTecker 的 CanPreventGestureCognizer: 方法允許或禁用 WKWebView 中的多個預定義手勢,具體取決於觸控操作設定

wkTouchActionGestureRecognizer(作為新增到 wkContentView 的最後乙個 GestureRecognizer) TouchBegin 設定 Recognized 狀態,這可能會使其他 GestureRecognizer 無法識別 (Cancelled)- VoidTouchesBegan:(nsset * )touches withevent:(uievent *)event- (void)touchesmoved:(nsset *)touches withevent:(uievent *)event- (void)touchesended:(nsset *)touches withevent:(uievent *)event- (void) TouchesCancelled:(nsset *)Touches withEvent:(uievent *)Event 此方法將GestureRecognizer 設定為識別成功狀態,允許其他 GestureRecognizers 或 HitTestView 停止接收事件(使用其他手勢衝突解決方法) - void) UpdateState 即使已成功識別其他手勢識別器,仍可識別此手勢識別器- (bool)canbepreventedbygesturerecognizer:(uigesturerecognizer *)preventinggesturerecognizer 如果此手勢識別器成功,請按 css touch-action 僅當設定了捏合縮放操作時,捏合縮放手勢才生效,只有在以下情況下才允許捏合縮放"pinch-zoom" or "manipulation" is specified. if (maypinchtozoom &&iterator->value.containsany())return yes;如果未設定任何設定,則雙擊縮放手勢以生效 只有在以下情況下才允許雙擊縮放"none" is specified. if (maydoubletaptozoom &&iterator->value.contains(webcore::touchaction::none)) return yes; }return no;}
wkContentView 中掛載了許多不同的 GestureRecognizers[3],要解決這些 GestureRecognizer 之間的衝突,您需要過載以下方法並在其中實現用於解決衝突的業務邏輯,詳見下面的注釋

wkContentView 工具方法 static inline bool issamepair(uigesturerecognizer *a, uigesturerecognizer *b, uigesturerecognizer *x, uigesturerecognizer *y) 此方法指定可以同時識別哪些手勢識別器,而不是傳送某人touchescancel- (bool)gesturerecognizer:(uigesturerecognizer *)gesturerecognizer shouldrecognizeSimultaneouslywithGesturerecognizer:(uigesturerecognizer*) othergesturerecognizer ..WkDeferringGestureRecognizer 之間沒有衝突 if ([GestureRecognizer iskindofclass:wkdeferringGestureRecognizer.class] &othergesturerecognizer iskindofclass:wkdeferringgesturerecognizer.class]) return yes;如果 (issamepair(gesturerecognizer, othergesturerecognizer, highlightlongpressgesturerecognizer.),則突出顯示手勢和長按手勢不衝突。get(),longpressgesturerecognizer.get())return yes;if h**e(uikit with mouse support) if ([GestureRecognizer IskindOfClass:[WkMouseGesturerecognizer class]] otherGesturerecognizer iskindofclass:[ wkmouseGesturerecognizer class]])返回 yes;Endif If Platform(MacCatalyst) 放大鏡與硬按文字手勢不衝突 if (issamepair(gesturerecognizer, othergesturerecognizer, [textinteractionassistant loupegesture], textinteractionassistant forcepressgesture]))return yes;單擊和放大鏡手勢不衝突,如果 (issamepair(gesturerecognizer, othergesturerecognizer, singletapgesturerecognizer..)get(),textinteractionassistant loupegesture]))return yes;查詢這是否與長按手勢不衝突 (((GestureRecognizer isKindofClass:[ UiLookUpGestureRecognizer Class]] OtherGestureRecognizer isKindofClass:[UiLongpressGestureRecognizer Class]]) otherGestureRecognizer isKindofClass:[UiLongPressGestureRecognizer Class]] GestureRecognizer isKindofClass:[ UiLookUpGestureRecognizer Class]])返回 yes;#endif // platform(maccatalyst) if (gesturerecognizer == _highlightlongpressgesturerecognizer.get() othergesturerecognizer == _highlightlongpressgesturerecognizer.get())以下邏輯注釋省略,感興趣的讀者可以閱讀 webkit 原始碼 if (issamepair(gesturerecognizer, othergesturerecognizer, singletapgesturerecognizer.get(),textinteractionassistant singletapgesture]))return yes; if (issamepair(gesturerecognizer, othergesturerecognizer, _singletapgesturerecognizer.get(),nonblockingdoubletapgesturerecognizer.get())return yes; if (issamepair(gesturerecognizer, othergesturerecognizer, _highlightlongpressgesturerecognizer.get(),nonblockingdoubletapgesturerecognizer.get())return yes; if (issamepair(gesturerecognizer, othergesturerecognizer, _highlightlongpressgesturerecognizer.get(),previewsecondarygesturerecognizer.get())return yes; if (issamepair(gesturerecognizer, othergesturerecognizer, _highlightlongpressgesturerecognizer.get(),previewgesturerecognizer.get())return yes; if (issamepair(gesturerecognizer, othergesturerecognizer, _nonblockingdoubletapgesturerecognizer.get(),doubletapgesturerecognizerfordoubleclick.get())return yes; if (issamepair(gesturerecognizer, othergesturerecognizer, _doubletapgesturerecognizer.get(),doubletapgesturerecognizerfordoubleclick.get())return yes;# if enable(image_extraction) if (gesturerecognizer == _imageextractiongesturerecognizer ||gesturerecognizer == _imageextractiontimeoutgesturerecognizer) return yes;#endif return no;} 此方法用於指示每個手勢識別器之間的優先順序,手勢識別器只有在othergesturerecognizer無法識別後才會識別- (bool)gesturerecognizer:(uigesturerecognizer *)gesturerecognizer shouldrequirefailureofgesturerecognizer: (uigesturerecognizer *)othergesturerecognizer 指定每個手勢識別器之間的優先順序,對於 deferringgesturerecognizer,如果需要延遲 otherGestureCognizer,這裡指定它的優先順序 - (布林值)GestureCoreCerver:(uiGestureCognDer*)GestureCognizer ShouldBeRequiredToFailbyGestureCognizer:(uiGestureCognizer *) otherGesturerecognizer
延遲識別其他手勢識別器有幾個主要用例:

如果多個手勢識別器掛載在同一檢視上,並且這些不同識別器可以識別的觸控序列包含共同的字首序列,則需要延遲這些識別器的成功識別,以確保所有識別器都有機會被識別。 另一種方案是前端支援在事件處理程式(如 touchstart)中禁用預設手勢 (event.)preventdefault())這要求在處理 Web 中的 touchstart 等事件時暫時推遲其他預設手勢識別。如果使用者在滾動檢視期間啟動觸控手勢,則不應延遲新手勢。 wkdeferringGestureRecognizer 實現延遲識別其他 GestureRecognizer 過程的主要機制是:

在 wkdeferringGestureRecognizer 的 TouchesStarted Touchesended 方法中,詢問其委託是否要此時推遲此事件。 如果您不需要延遲,只需設定 self 即可State = UIIdementRecognizerStateFailed,在這種情況下,它對其他手勢識別沒有影響; 如果需要延遲,則其手勢識別狀態不會更改,因此在失敗後依賴手勢識別器進行識別的其他識別操作將被延遲。 touchesbegin 判斷延遲是否基於對應的觸控的邏輯view 不是 scrollview,如果 scrollView 正在互動(spi:isInterruptingDeceleration),如果是,則不 defer,否則 defertouchesend,判斷前端當前是否正在處理 touchStart 事件(此事件可以阻止其他手勢),如果是,則 DeferCanBePreventedByGestureRecognizer 直接返回 no,表示不會因為成功識別其他 GestureRecognizer 而強制取消

wkdeferringGesturerecognizer- (void)TouchesBegin:(NSSET *)Touches withEvent:(UIevent *)Event- (void)TouchesEnded:(NSSET *)Touches withEvent:(UITature *)Event- (void) TouchesCancelled:(nsset *)Touches withevent:(uievent *)Event 不能被其他 GestureRecognizer 取消 - (bool)CanBePreventedByGestureRecognizer:(uiGestureRecognizer *) preventinggesturerecognizer
在 wkcontentview 的手勢衝突處理程式中,呼叫以下方法來解決手勢字首序列問題。

手勢衝突相關方法呼叫鏈:-[wkContentView GestureRecognizer:shouldrequirefailureofgesturerecognizer:] 呼叫:-[wkdeferringGestureRecognizer shoulddeferGesturerecognizer] 呼叫:-[ wkContentView DeferringGestureRecognizer:ShouldDeferOtherGestureRecognizer:] wkContentView 確定是否需要延遲此 DeferringGestureRecognizer GestureRecognizer- (bool) deferringGesturerecognizer:(wkdeferringgesturerecognizer *)deferringgesturerecognizerShouldDeferOtherGestureRecognizer:(UIEcementRecognizer *)GestureRecognizer 檢視 = 檢視。superview;如果 (!.)gestureisinstalledonorunderwebview) return no;如果出現以下情況,則不應延遲其他 DeferringGestureRecognizers。class]) return no;如果 (gesturerecognizer == toucheventgesturerecognizer) return no,則不應延遲 Web Touch 手勢;  auto maydelayresetofcontainingsubgraph = [&uigesturerecognizer *gesture) -bool return no;} 雙擊,點選手勢應延遲 if (gesturerecognizer == doubletapgesturerecognizer ||gesturerecognizer == _singletapgesturerecognizer) return deferringgesturerecognizer == _deferringgesturerecognizerforsynthetictapgestures; if (maydelayresetofcontainingsubgraph(gesturerecognizer)) return deferringgesturerecognizer == _deferringgesturerecognizerfordelayedresettablegestures; return deferringgesturerecognizer == _deferringgesturerecognizerforimmediatelyresettablegestures; #else unused_param(deferringgesturerecognizer); unused_param(gesturerecognizer); return no; #endif }
[1] iOS 中的事件響應:CSS 觸控操作文件:每個 GestureRecognizer 在 WKWebView 中的作用:

相關問題答案

    如何比較八的手勢

    手勢八是我們日常生活中經常使用的一種表達方式,它簡潔易懂。以下是手勢手勢八的詳細說明。首先,讓我們明確一點,八的手勢是用兩隻手完成的。具體來說,我們首先將雙手的食指放在一起,形成乙個 V 形。這個手勢代表數字 一 接下來,我們將中指放在食指下方,形成 y 的形狀。這個手勢代表數字 二 然後,我們將雙...

    CTC 分公司 習 解決語音識別中的序列對齊挑戰

    語音識別是人工智慧領域的乙個重要研究方向,旨在將人類語音轉化為文字形式。然而,在語音識別中,序列對齊是乙個棘手的問題。為了解決這一難題,研究人員提出了一種用於CTC分支的習方法。在本文中,我們將介紹CTC分支習的原理和應用,以及它在解決語音識別中序列對齊挑戰中的重要性。.序列比對問題的挑戰。在語音識...

    恭喜拳頭敬禮的正確手勢

    握拳儀式,又稱弓手儀式,是起源於中國古代的一種禮儀,也是武術中的一種問候禮儀。拳禮的正確手勢是 右手握拳,左手拇指扣右手掌心,其餘四根手指伸直,雙臂從身體兩側向前合攏,右手在外,左手在內,兩隻手掌略帶 手呈弧形。握拳儀式是中國傳統的文化禮儀,常用於武術比賽 頒獎典禮等場合。握拳的正確手勢代表謙遜 尊...

    戀愛中的紅線和底線 如何識別不負責任的伴侶

    愛,就像乙個花園,需要用心去耕耘和呵護。真愛不僅僅表現在言語上,而是通過日常生活的細節來表達。但是,如果這個花園中的一方無視界限,忽視責任,那麼愛情就會逐漸枯萎。有些男人,在和你相處的時候,往往不考慮你的感受,一次又一次地觸碰你的底線。他們可能不明白,每一次越界,都是對你心中美好嚮往愛情的傷害。就像...

    面對家庭暴力的情緒操縱:識別和自我保護策略

    家庭暴力不僅是身體上的傷害,而且往往伴隨著情感操縱,這是一種心理虐待。情感操縱在家庭暴力中更為微妙,但也極具破壞性。識別和應對情緒操縱對於受害者保護自己至關重要。.情緒操縱的常見表現 操縱感情 操縱者通過貶低 威脅 恐嚇或感到內疚,使受害者感到無助和依賴。隔離 操縱者試圖通過切斷受害者與外界的聯絡來...