落格输入法是如何处理按键消息的

栏目: IOS · 发布时间: 5年前

内容简介:最近更新:30th 四月, 2019要做一款移动设备上的软键盘,那么怎么处理用户的点击位置,就是你遇到的第一个难题,在这个问题上,我也走了很长的路。我把落格输入法开发以来的触控逻辑大致分类为三个阶段,现在分别来讲讲设计思路,希望能够对你有所帮助。

最近更新:30th 四月, 2019

要做一款移动设备上的软键盘,那么怎么处理用户的点击位置,就是你遇到的第一个难题,在这个问题上,我也走了很长的路。

我把落格输入法开发以来的触控逻辑大致分类为三个阶段,现在分别来讲讲设计思路,希望能够对你有所帮助。

第一代触控引擎

显然,对于一个初学者来说,没什么比系统控件更好用的了,功能全,速度也不慢,业务逻辑完善,所以,落格输入法的第一代消息处理就是用的 UIButton TouchUpInside 消息。因为一开始我甚至用的是 xib 构建键盘布局,所以直接使用了 @ IBAction func buttonTouchUpInside ( _ sender : UIButton ) 这样的声明,你一看就懂了,对吧?处理很方便,用户点击了哪个 Button,那么传进来的就是哪个,不需要做额外的判断,系统都帮你搞定了一切。但很快就遇到了第一个问题——按下按钮后程序总要执行一会,于是 UI 就会卡顿,很明显从按下按键到候选出现,会有延迟。

为了避免输入法业务逻辑干扰(实际上是阻挡)UI 更新,我改为在后台处理进一步的消息,实际上你也应该总是这么做——永远不要在主线程处理业务逻辑。

DispatchQueue.global(qos: .userInitiated).async {
            self.input(button:button)
        }

但这样又引入了另一个问题,当用户点击按键间隔太短速度太快时,按键处理的顺序会错乱,于是,我把异步改为同步,这样业务逻辑还是在后台线程处理,但会严格按照调用的顺序依次执行(这确保了用户按键以实际顺序进行处理)

DispatchQueue.global(qos: .userInitiated).sync {
            self.input(button:button)
        }

注意第一行,async → sync。

第二代触控引擎

第一代引擎正常工作了很久,但最终还是遇到了另一个问题:当用户点击更加快的时候,某些后台逻辑处理不正常。

这实际上是由于 UIButton 自身触控处理机制冲突造成的,当用户点击屏幕键盘速度太快,实际上短时间内同时按下了两个按钮,此时主线程自身是互相阻止的,只有当用户两个手指都离开屏幕,消息才会发送,即 @ IBAction func buttonTouchUpInside ( _ sender : UIButton ) 被立即调用两次。在极短时间内,两次连续调用,虽然是有严格顺序的,但每个按键消息都会处理候选栏的刷新,这就需要异步更改 UI,这就导致了一些处理逻辑异常——在 UI 刷新完成之前,下一个消息已经开始执行。

解决的思路有两个,要么把业务逻辑的一些判断改为和 UI 不关联,要么想办法让系统能够处理用户同时按下多个 UIButton 的情况。 ——显然,应该从后者入手,于是,我在开发 落格输入法 X 时做了一个按键缓存机制,它不再使用 TouchUpInside ,而是 TouchDown TouchUp

var pendingKey:UIButton?
@IBAction func keyboardKeyDown(_ sender:UIButton) {
        if sender == pendingKey {return}
        
        if let pending = pendingKey {
            keyboardKeyConfirm(pending)
        }
        pendingKey = sender
    }
 
@IBAction func keyboardKeyUp(_ sender: UIButton) {
        
        guard let pending = pendingKey, sender.tag == pending.tag else {return}
        keyboardKeyConfirm(pending)
        pendingKey = nil
    }
func keyboardKeyConfirm(_ sender:UIButton) {
        buttonTouchUpInside(sender)
    }

注意 buttonTouchUpInside 就是上一代的处理逻辑,并没有什么变化,这么写是为了让你明白我的变更思路,这样当用户按下一个键,那么就立即缓存它,当用户再按下一个键,如果已经有缓存了,那么就处理它,并将新的加入缓存;当用户抬起手指,那么就处理缓存的那个按键。如此一来,所有的按键都会得到处理,并且不会被 TouchUpInside 这个信号阻拦——因为我已经不再使用它了。

由于更改了信号获取源头(从 TouchUpInside 改为 TouchDown + TouchUp ),对用户来说“手感”变化很大。

第三代触控引擎

第二代实际上已经工作的很好,但在 落格输入法 X 上架之前,我们内部测试就发现了另一个问题——实际上并不能说是新发现的,因为它一直都存在,那就是“q”和“p”的问题。在新的 iOS 系统当中,似乎是为了避免和系统手势冲突,iOS 为屏幕边缘的 5 个像素做了保留处理,当你点击到屏幕边缘的时候(即按按键“q”或者“p”时稍微靠边了点), TouchDown 这个消息是不会立即被触发的。

它会被延迟到你抬起手指的那一刻,然后和 TouchUp 一起发送给键盘。这就导致了用户正常打字的时候,遇到这两个位置,总是会感觉“卡顿”了一下,因为视觉和声音反馈上,确实是延迟到你抬起手指的那一刻而不是按下就立即触发。

如果是在 app 当中,你可以这样做:

override var preferredScreenEdgesDeferringSystemGestures: UIRectEdge {
        return [.left,.right]
    }

但显然,在键盘中这个代码并不生效。总之,为了让即将上架的 落格输入法 X 更加具有竞争力,我只好硬着头皮继续想办法。

我在尝试了很多方案之后,我终于找到了一个能获取信号的控件—— UITapGestureRecognizer

不是用它来进行标准识别点击——这同样是没用的,必须使用它的 func gestureRecognizer ( _ gestureRecognizer : UIGestureRecognizer , shouldReceive touch : UITouch ) -> Bool 这个代理方法,只有它能够越过系统屏蔽,正确获得 TouchDown 调用,而不是被延迟到用户抬起手指的那一刻。

对应地,我又使用 UIView 本身的 func touchesEnded ( _ touches : Set < UITouch > , with event : UIEvent ? ) 来获取 TouchUp 行为,如此一来,就可以参考上一代引擎的逻辑实现了:

class TouchLayer:UIView,UIGestureRecognizerDelegate {
    var tapGR = UITapGestureRecognizer()
    init(keyboard:KeyboardViewController) {
        super.init(frame: CGRect.zero)
        tapGR.delegate = self
        self.addGestureRecognizer(tapGR)
    }
    var currentTouch:UITouch?
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        if let t = currentTouch {
            touchUpInside(t)
        }
    }
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        if let t = currentTouch {
            touchUpInside(t)
        }
        touchDown(touch)
        return true
 
    }
}

其他代码略过不表,这样操作之后,把这个 TouchLayer 覆盖在键盘上方即可——当然,你也可以在你的 Button 子类里进行这样的操作,然后单独处理。这里我则盖在键盘上方,进行统一处理了。

为此,我不得不对键盘的业务逻辑进行了一番调整和重构……直到去年年底,我写了一篇文章 落格输入法 X 是如何处理屏幕边缘延迟问题的

总之,这就是现在线上版本 落格输入法 X 在使用的触控逻辑,目前来看,一切良好。如果说缺点,大概就是从二代升级三代代价实在是太大了,这几乎是 落格输入法 X 与 经典版 的重点区别之一了。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Effective java 中文版(第2版)

Effective java 中文版(第2版)

Joshua Bloch / 俞黎敏 / 机械工业出版社 / 2009-1-1 / 52.00元

本书介绍了在Java编程中78条极具实用价值的经验规则,这些经验规则涵盖了大多数开发人员每天所面临的问题的解决方案。通过对Java平台设计专家所使用的技术的全面描述,揭示了应该做什么,不应该做什么才能产生清晰、健壮和高效的代码。 本书中的每条规则都以简短、独立的小文章形式出现,并通过例子代码加以进一步说明。本书内容全面,结构清晰,讲解详细。可作为技术人员的参考用书。一起来看看 《Effective java 中文版(第2版)》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换