我們都直到,第三方輸入法比如搜狗輸入法有個經典的手勢操作——在鍵盤上左右劃動即可移動光標。而這個功能我自己也十分的常用,所以,我想要自己來實現它。
首先我想到的就是 UISwipeGestureRecognizer ,不過結果可想而知,劃動一次只能移動一格光標,這可不是我想要的。
看來唯一的辦法就是用 UIPanGestureRecognizer 來自己實現了。
最初的想法
最一開始,我想到的就是像捕捉拖動一樣來落去手指在鍵盤上的位置,我只需要獲取 x 軸,如果是正的就往右,負的就往左。
代碼看起來大概像這樣:
1 2 3 4 5 6 7 |
@IBAction func moveCurser(sender: UIPanGestureRecognizer) { if sender.translationInView(keyboardView).x < 0 { self.textDocumentProxy.adjustTextPositionByCharacterOffset(-1) } else { self.textDocumentProxy.adjustTextPositionByCharacterOffset(1) } } |
因為我只有按照相對方向移動光標的權限,所以只好如此。當然,缺點就是不論移動的快慢,光標的移動速度是與函數調用的頻率一致的。
另一個致命的缺點是如果我手指向一個方向劃動比較遠,再移動回來——這個過程中光標依然會傻傻地超先前的方向繼續移動!
改用加速度而不是坐標作為判斷
好吧,這次我改用 UIPanGestureRecognizer 內置的 velocityInView 來獲取加速度,因為加速度是有方向的!顯然,光標傻傻的朝一個方向移動的問題解決了……可是,它還是依靠系統調用頻率來決定移動的速度,這就導致了光標移動太快。
對系統調用頻率濾波
好在,系統的調用頻率是固定的,這樣的話我們可以輕易地實現濾波,加一個屬性,用來儲存調用的次數,當次數達到了指定的值比如調用了100次,我們才真正執行一次函數:
1 2 3 4 5 6 7 8 9 10 11 |
var recognizerCount = 0 @IBAction func moveCurser(sender: UIPanGestureRecognizer) { recognizerCount += 1 guard recognizerCount > 100 else {return} recognizerCount = 0 if sender.velocityInView(keyboardView).x < 0 { self.textDocumentProxy.adjustTextPositionByCharacterOffset(-1) } else { self.textDocumentProxy.adjustTextPositionByCharacterOffset(1) } } |
根據移動速度動態改變光標移動速度
這下光標的移動終於顯得比較合適了,那麼接下來的問題就是光標的移動速度改變了,現在移動速度是固定的,無論你是想要快一點移動還是慢一點移動,都不行。要達到這一目的,我們還得在加速度上做文章。
加速度會根據你手指在屏幕上移動的速度而改變,簡而言之就是你劃動的越快,那麼加速度就越大——這麼一來我們就有了兩個方案:
- 根據加速度改變一次移動光標的字數;
- 根據加速度跟邊函數調用的頻率。
方案 1 比較簡單,把加速度計算到個位數然後直接扔給 adjustTextPositionByCharacterOffset(_:) 就好了,可是這有個問題——定位精確度會下降。這裡我選擇了更類似搜狗輸入法的後者,我依舊每次移動光標 1 個字符的距離,只需要改變函數的調用頻率——也就是說,改變我們的濾波算法就好了。
現在的濾波只是單純的每 100 次取 1 次,那麼我們提前獲取一次加速度,把它算進去試試,由於加速度有方向——你總不能給濾波器增量減回去吧?我們用 ABS 全局數學函數來求一下絕對值,再進行處理,同時由於加速度變化巨大,經過多次測試,縮小10倍比較合適,那麼代碼看起來就是這個樣子的:
1 2 3 4 5 6 7 8 9 10 11 12 |
var recognizerCount = 0 @IBAction func moveCurser(sender: UIPanGestureRecognizer) { let v = Int(sender.velocityInView(keyboardView).x) recognizerCount += Int(abs(v)/10) guard recognizerCount > 100 else {return} recognizerCount = 0 if sender.velocityInView(keyboardView).x < 0 { self.textDocumentProxy.adjustTextPositionByCharacterOffset(-1) } else { self.textDocumentProxy.adjustTextPositionByCharacterOffset(1) } } |
這樣,函數實際的調用頻率會由濾波器決定,而濾波器的增量又由你實際劃動時在屏幕上產生的加速度來決定,從而實現了光標跟手指幾乎同步移動的效果。
誤操作兼容
最後,我們再來點收尾的工作。由於識別層在鍵盤的按鈕下邊,那麼我們就要讓它們合作無間才行—— UIPanGestureRecognizer 會立即取消 的UIButton 的點擊操作,也就是說,如果用戶打字比較快,而手指又不小心在鍵盤上搓了一下,那麼系統就會識別為微小但加速度很大的劃動而不是點擊。
所以,第一步,我們需要取消 cancelsTouchesInView ,給它賦值為 false 或者在圖形配置裡取消這個的勾選。
光這一點還不夠,我們還得繼續加大相應的閾值,在濾波器前邊再獲取拖動的絕對值,如果拖動小於一定的距離,就同樣不執行函數,給用戶留下一定的緩衝空間,只有用戶真的想要劃動來移動光標的時候才去移動光標,那麼,最終的函數應該是這樣的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
var recognizerCount = 0 @IBAction func moveCurser(sender: UIPanGestureRecognizer) { guard abs(sender.translationInView(keyboardView).x) > 70 else {return} let v = Int(sender.velocityInView(keyboardView).x) recognizerCount += Int(abs(v)/10) guard recognizerCount > 100 else {return} recognizerCount = 0 if sender.velocityInView(keyboardView).x < 0 { self.textDocumentProxy.adjustTextPositionByCharacterOffset(-1) } else { self.textDocumentProxy.adjustTextPositionByCharacterOffset(1) } } |
本文由 落格博客 原創撰寫:落格博客 » iOS 自定義鍵盤 左右劃動移動光標 實現
轉載請保留出處和原文鏈接:https://www.logcg.com/archives/1987.html