IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    iOS 自定义键盘 左右划动移动光标 实现

    R0uter发表于 2016-09-05 09:30:37
    love 0

    去落格博客阅读完整排版的iOS 自定义键盘 左右划动移动光标 实现

    我们都直到,第三方输入法比如搜狗输入法有个经典的手势操作——在键盘上左右划动即可移动光标。而这个功能我自己也十分的常用,所以,我想要自己来实现它。

    首先我想到的就是

    UISwipeGestureRecognizer
     ,不过结果可想而知,划动一次只能移动一格光标,这可不是我想要的。

    看来唯一的办法就是用

    UIPanGestureRecognizer
     来自己实现了。

    最初的想法

    最一开始,我想到的就是像捕捉拖动一样来落去手指在键盘上的位置,我只需要获取 x 轴,如果是正的就往右,负的就往左。

    代码看起来大概像这样:

    @IBAction func moveCurser(sender: UIPanGestureRecognizer) {
            if sender.translationInView(keyboardView).x < 0 {
                self.textDocumentProxy.adjustTextPositionByCharacterOffset(-1)
            } else {
                self.textDocumentProxy.adjustTextPositionByCharacterOffset(1)
            }
        }

    因为我只有按照相对方向移动光标的权限,所以只好如此。当然,缺点就是不论移动的快慢,光标的移动速度是与函数调用的频率一致的。

    另一个致命的缺点是如果我手指向一个方向划动比较远,再移动回来——这个过程中光标依然会傻傻地超先前的方向继续移动!

    改用加速度而不是坐标作为判断

    好吧,这次我改用

    UIPanGestureRecognizer
     内置的
    velocityInView
     来获取加速度,因为加速度是有方向的!显然,光标傻傻的朝一个方向移动的问题解决了……可是,它还是依靠系统调用频率来决定移动的速度,这就导致了光标移动太快。

    对系统调用频率滤波

    好在,系统的调用频率是固定的,这样的话我们可以轻易地实现滤波,加一个属性,用来储存调用的次数,当次数达到了指定的值比如调用了100次,我们才真正执行一次函数:

    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. 根据加速度改变一次移动光标的字数;
    2. 根据加速度跟边函数调用的频率。

    方案 1 比较简单,把加速度计算到个位数然后直接扔给

    adjustTextPositionByCharacterOffset(_:)
     就好了,可是这有个问题——定位精确度会下降。这里我选择了更类似搜狗输入法的后者,我依旧每次移动光标 1 个字符的距离,只需要改变函数的调用频率——也就是说,改变我们的滤波算法就好了。

    现在的滤波只是单纯的每 100 次取 1 次,那么我们提前获取一次加速度,把它算进去试试,由于加速度有方向——你总不能给滤波器增量减回去吧😂我们用

    abs
     全局数学函数来求一下绝对值,再进行处理,同时由于加速度变化巨大,经过多次测试,缩小10倍比较合适,那么代码看起来就是这个样子的:

    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 或者在图形配置里取消这个的勾选。

    光这一点还不够,我们还得继续加大相应的阈值,在滤波器前边再获取拖动的绝对值,如果拖动小于一定的距离,就同样不执行函数,给用户留下一定的缓冲空间,只有用户真的想要划动来移动光标的时候才去移动光标,那么,最终的函数应该是这样的:

    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 自定义键盘 左右划动移动光标 实现,首发于落格博客。

    其他推荐:
    1. 一道 华为 面试 的 编程算法 题
    2. “plugin invalidated” ios 自定义键盘
    3. 泛型
    4. Swift 里的 单件模式
    5. 总会报错:异常处理



沪ICP备19023445号-2号
友情链接