1. 程式人生 > IOS開發 >ARKit 中如何使用傳統的二維手勢?

ARKit 中如何使用傳統的二維手勢?

AR 中的手勢操作

可能大家看了標題,都沒明白:AR 中的二維手勢是個什麼玩意?

其實就是給 AR 物體貼圖,在貼圖中放一個 UIButton 或 UIScrollView,又或者自定義 View, 看它們能不能正常響應手勢,以及能響應哪些手勢。

關於貼圖可以看 SCNNode到底應該怎麼貼圖?UIImage,UIImageView,CALayer用哪個?

需要注意的是:蘋果官方目前(至 iOS13)都並不推薦貼圖使用 UIView 類,可能會有潛在的佈局問題,記憶體問題等。蘋果論壇上官方員工的回覆forums.developer.apple.com/message/340…

哪些手勢還可以使用?

經過測試發現,平時我們使用各種手勢,在 AR 中基本都是可以使用的。包括:UIButton 的各種手勢,UIScrollView 拖拽手勢,UITapGestureRecognizer 手勢, 甚至是 UICollectionView 的選中 cell 手勢都可以正常工作。

如下圖:淺灰色背景是 UIView;左上角放置兩個 UIButton,黑色和深灰色;右下角大塊的是 UICollectionView,三個 cell 顏色不同。

需要注意的是:當給整個 View 添加了 UITapGestureRecognizer 手勢後, UICollectionView 的選中 cell 手勢就不再響應了。如下圖:

觸發問題

在開發中遇到常見問題,一個是 AR手勢與螢幕手勢優先順序問題,這個下面再講。另一個是 AR 中 UIButtontouchUpInside手勢,有時難以觸發的問題。

尤其是在距離 AR 平面比較遠(2 米外),沒有正面對著平面時(斜著點選),非常嚴重。經常可以看到按鈕已經被按下並處於高亮狀態了,但是閃一下後又退出了高亮狀態,而手勢並沒有被觸發。

我想可能是因為touchUpInside要求手指按下時和離開時,都要在按鈕內部,這樣才會觸發。而 AR 中的按鈕要想被點選,需要一隻手拿著手機,另一隻手點選螢幕中 AR 平面的區域,這樣造成的點選時抖動太大。

這樣在 AR 中的按鈕看來:手指按下了按鈕(touchDown),然後抖動嚴重,跑到了按鈕外面(outside),這樣是不符合 touchUpInside 定義的,所以不會觸發。

為了按鈕手勢更好的觸發,將按鈕觸發條件改為touchDown,可以有效提高 AR 按鈕點選響應的概率。

還有一個類似的問題,是 AR 中 UIScrollView 的拖拽手勢在拖動時經常會中斷和抖動,尤其是從側面進行拖拽時。好在這個問題不太嚴重,也沒有什麼太好的處理方法。

iOS 13 中 UICollectionView 的新問題

原本在 iOS 12 中只有 UIButton 的touchUpInside手勢會出現難以響應的問題,更新 iOS 13 後,UICollectionView 選中 cell 手勢(didSelectItemAt)也出現了幾乎一模一樣的選中困難問題,嚴重時點選 10 次都會觸發一次。

為了緩解這個問題,不得不給整個 UICollectionView 添加了 UITapGestureRecognizer 手勢,來代替 didSelectItemAt 事件。

手勢的優先順序問題

經過測試,發現優先順序:AR 貼圖手勢 > 螢幕手勢,哪怕你的 ARSCNView 在下面,而螢幕手勢被新增的 UIView 層級更高,也仍然是 AR 中的手勢先觸發。

具體來說:AR 中 UIButton 手勢(touchUpInside/touchDown) > AR 中 UIScrollView 拖拽手勢 > AR 中的 UITapGestureRecognizer 手勢 > AR 中的 UICollectionView 選中 cell 手勢(didSelectItemAt) > 螢幕上的 UIButton 手勢 > 螢幕中的 UITapGestureRecognizer 手勢....

下面,我們給螢幕中新增一個 UIButton,然後測試點選它,注意觀察當它後面有 AR 內容時,和沒有 AR 內容時,響應是不一樣的。只有當 screenButton 後面的 AR 物體不能響應時,screenButton 的手勢才能觸發(arButton2 在錄製時沒點選到)

如果把螢幕上的 UIButton 換成對整個 View 新增 UITapGestureRecognizer 手勢,結果也是一樣的

即使你在控制器介面實現touchesBegan方法也是一樣,低優先順序被觸發:

override func touchesBegan(_ touches: Set<UITouch>,with event: UIEvent?) {
}
複製程式碼

思考與總結

實踐一番下來,蘋果的各種手勢基本都能在 AR 貼圖中使用,即使個別有問題也可能通過變通的方式來解決。

真正麻煩的是 AR 貼圖手勢與螢幕手勢之間的優先順序問題。一旦貼圖中有了手勢,它就會永遠處於第一級響應者,導致我們在螢幕上的 UI 控制元件無法響應。這無疑給我們開發時增加了困難。

方案一

為了相容兩種手勢事件,不得不在螢幕上有控制元件需要點選時,臨時禁用 AR 貼圖中的手勢。螢幕控制元件消失後,再啟用貼圖中的手勢

方案二

但是如果螢幕上確實有控制元件需要一直顯示並等待點選,而 AR 貼圖中也需要一直顯示並等待點選,該怎麼辦呢?比如在 AR 貼圖中有個按鈕,螢幕上也有個按鈕,當用戶把兩個按鈕重合時,點選一下,響應肯定的是 AR 中的按鈕,這卻並不是我們想要的結果。

暫時想到的方法是,用下面的方法來把 AR 中按鈕的 3D 位置,投影到螢幕上,看看螢幕上對應位置有沒有需要響應的按鈕

func projectPoint(_ point: SCNVector3) -> SCNVector3
複製程式碼

但是這樣也會有很多複雜步驟:

  • 先獲取 AR 貼圖中按鈕被點選位置的 2D 座標;
  • 再根據貼圖尺寸,以及按鈕在貼圖中的 2D 座標計算其在貼圖中的相對座標;
  • 根據 SCNNode 的幾何形狀,計算貼圖平面在 3D 空間的位置;
  • 根據上面兩步的 3D 位置與 2D 座標,計算點在 3D 空間中的真實位置;
  • 將 3D 位置座標投影到ARSCNView中;
  • ARSCNView 座標位置處是否有螢幕按鈕;
  • 點選事件需要傳遞處理......

建議

所有這些都嚴重增加了開發的複雜度,造成得不償失。所以還是建議少在 AR 貼圖中使用手勢,如果必須要使用就注意減少與螢幕控制元件的手勢衝突。

另外,使用 UIView 作為 SCNNode 的貼圖,疑似存在記憶體洩露的問題:View 和 Node 都銷燬了,但 app 的總記憶體卻不斷上漲。需要大家在開發中注意,暫無好的解決辦法。