inputView与inputAccessoryView

也许是习惯了没有文字输入的交互,一旦遇到定制化的输入交互时,麻烦就来了。需求经常是:

(1)仅输入数字或字母,或者它们的组合

(2)输入长度限制

(3)限制特定类型的文本输入,例如手机号码,电话号码,车牌号

(4)根据输入类型,分隔输入文本,例如手机号使用3-4-4格式,车牌号使用多个分离的输入框,并且要符合命名规则,等等

(5)禁止输入emoji表情

(6)自定义输入表情

这种情况下,如果首先想到直接使用UITextField或者UITextView,前者没有textDidChange的代理,仅支持单行输入,后者则更完善和强大,但除了最后一个需求外,最常用的方式还是在textShouldChange的代理中使用正则匹配来完成,只是后续的处理会十分麻烦。由于iOS 8引入了keyboard extension以及后续出现了官方的九宫格输入法,会让一些本来无法输入其他字符的场景也能过输入一些emoji表情或出现截断。

此外还有选中,粘贴,以及更复杂的图文混排等。有一位同事习惯说没有程序员做不了的东西,只要看产品提的需求合不合理,由于UI设计师相当霸道,所以大家基本都忘了交互这个东西,而且经常copy的话,更容易忘记交互设计这个环节,这就导致了一个很严重的问题,程序员在写完需求代码后,发现了交互上的逻辑漏洞,那么这时只能靠程序员脑补交互来填坑了。如果是有经验的程序员还好,会直接拿做过的东西(一般也匹配已有的需求)来完善,像wbuntu这样的菜鸟,只能去问了,这种自寻死路的方式,通常会触发新需求的诞生,导致拖延和撕逼。

最粗暴的方式自定义键盘方式,就是修改inputView与inputAccessoryView

// Presented when object becomes first responder.  If set to nil, reverts to following responder chain.  If
// set while first responder, will not take effect until reloadInputViews is called.
@property (nullable, readwrite, strong) UIView *inputView;             
@property (nullable, readwrite, strong) UIView *inputAccessoryView;

这两个属性从iOS 3.2引入,相比后期引入的keyboard extension要早得多,在iOS 8以前的系统,UC浏览器、百度贴吧和一些QQ产品中,会有启用云输入法这个选项,选中后就能绕开系统输入法,使用应用提供的输入法,但仅限于应用内使用,它们修改的应该就是默认为空的inputView和inputAccessoryView。

inputView可以作为一个独立的视图来绘制,被唤起时,也能通过监听键盘事件来获取高度,使用的方法很简单:把需要的按键全部以自定义视图的方式绘制出来,然后处理好所有触摸操作。下面就是我自己绘制的纯数字键盘,在iOS 8.4模拟器中截图,一眼看上去的话,应该和系统自带的没有太大区别。

首先在手机上截取了系统自带的键盘图片,然后使用取色器和预览获取颜色和尺寸。

自定义键盘的缺点也有,不会触发textShouldChange与textDidChange相关的代理方法,清空文本框的按键也不会生效(文本变化相关的事件都需要自己处理),此外的其他代理方法会正常触发。可以引入代码块,将自定义输入视图设定为textField或textView的代理,在对应方法中调用block。

优点是可以实时获取和修改文本内容,在点击时就处理输入逻辑,而且还有一个好处,即使用户把所有输入法删除,只留下第三方输入法时,也不会唤起第三方输入法(摩拜的手动车牌输入中还可以输入emoji,它使用的是隐藏textFiled的做法,不信试试~)。而且如果使用了多个UITextField时且都使用了同一个inputView时,切换firstResponder时也不会出现卡顿。

这里是最简单的数字输入,其他情况,比如省份,城市等选项较少的类型,也可以组成键盘输入的形式,而实现拼音之类的.......工作量还是太大了。从输入源头上直接限定输入内容,应该是最安全的方法了,不会引起错误穿透到服务端。如果是使用正则表达式,遇上需求、交互不明确或者新系统的问题,比如过滤emoji输入的正则把九宫格点击输入也捕获的情况,就没办法了。