捣鼓了半天 终于把自己的博客搭建好了,发一篇文章试试手。公司里的服务器比较low,不能支持emoji表情,本意是解决这个问题,自定义一个UITextField的控件。后来索性把长度校验也做了进去,基本满足了正常的需求。
一 限制文本长度
目前textfield的输入大概就2种:
通过点击键盘按键输入的
通过点击键盘联想输入的 [同时有高亮字符(maskText)占位]
首先参考了一些文章:
主要我看这篇文章比较全
http://www.jianshu.com/p/2d1c06f2dfa4
网上能搜索到的主要在这篇文章里都有体现了,但是也是有问题的。
先说说实现的方法
对应上述3种情况限制长度的主要方法
点击键盘输入的不管中英文点击键盘就能唤起UITextFieldDelegate
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
所以通过这个方法可以达到过滤掉键盘输入的文字超长的效果。
- 中文输入等 都是通过点击键盘后选取键盘联想也就是上述第二种情况,在上面的代理中无法监听,不过UITextField身为UIControl 可以通过
[self addTarget:<#(nullable id)#> action:<#(nonnull SEL)#> forControlEvents:<#(UIControlEvents)#>]
通过监听UIControlEventEditingChanged 来实现效果
==注意:在需要高亮字符占位的输入法(不仅只有中文输入法),在点击键盘按键输入时(拼音,笔画等)还是能被第一种情况的代理监听,只有当选取联想出来的文字的时候才不能。==
反例分析
现在网上搜索一大堆限制文字长度的方法大概是这样(我就用上述连接上的代码了反正都类似)
-(void)textFiledEditChanged:(NSNotification *)obj{
UITextField *textField = (UITextField *)obj.object;
NSString *toBeString = textField.text;
NSString *lang = [[UITextInputMode currentInputMode] primaryLanguage]; // 键盘输入模式
if ([lang isEqualToString:@"zh-Hans"]) { // 简体中文输入,包括简体拼音,健体五笔,简体手写
UITextRange *selectedRange = [textField markedTextRange]; //获取高亮部分
UITextPosition *position = [textFieldpositionFromPosition:selectedRange.start offset:0];
// 没有高亮选择的字,则对已输入的文字进行字数统计和限制
if (!position) {
if (toBeString.length > kMaxLength) {
textField.text = [toBeString substringToIndex:kMaxLength];
}
} // 有高亮选择的字符串,则暂不对文字进行统计和限制
else{
}
} // 中文输入法以外的直接对其统计限制即可,不考虑其他语种情况 else{
if (toBeString.length > kMaxLength) {
textField.text = [toBeString substringToIndex:kMaxLength];
}
}}
首先看代码就觉得这个是用通知的形势来发送UIControlEventEditingChanged的事件,这明显不可取,同一个页面可能有多个textField这玩意还要做个校验通知是谁发出来的。
然后他写死限制了中文输入法,这不是很可取,日语,韩语输入法的形式和中文差不多,还有特意过滤的高亮状态,对高亮状态的字不进行字数统计,这明显是错的。高亮状态的时候,可以不停的输入在还没有结束输入之前完全可能超过字数限制,这时候直接取textField.text的长度就超过了要求。
解决方案
针对多个控件发通知的问题,使用
[self addTarget:<#(nullable id)#> action:<#(nonnull SEL)#> forControlEvents:<#(UIControlEvents)#>]
通过监听UIControlEventEditingChanged 明显更加合理。没必要特别排除高亮状态,高亮状态仍然应该属于校验范围内。
还有一点比较隐蔽,就是这时候就算字数超过被截去,用户仍然可以不停的按键盘,这时候,自动联想还能继续,导致自动联想出来的字数越来越多,交互很不合理,用户体验不佳。这时候要同时实现
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
限制用户点击的键盘输入。这里可能有人会说 ==这样在输入全部是英文的情况下是可以的. 但是当输入是中文时, 由于shouldChangeCharactersInRange判断的是当前键盘的字符数, 会出现这样的问题: 比如你还剩下2个字可以打, 你想输入"张三", "张"的拼音是Zhang, 于是你在输入Zh的时候就无法输入了. 显然, 这样的结果不是我们想要的.== 我再强调下但是这必须要这样。因为如果你不限制高亮字符让其出现在文本框内,使用控件的同学直接取textField.text这时候的文本自然包含高亮文本,导致长度就超过了要求(如 要求是5个字不进行限制,输入xxx后再输入张三的拼音得到 xxx==zhangsan==这时候张三还在高亮状态,然后用户直接点击保存,这时候如果使用控件的人不先进行 endEditing操作,然后直接取textField.text 取得的文本肯定是 xxxzhangsan 超过了5个字的要求,控件限制文本长度的功能不能依赖与控件使用者,所以不可取)
Demo
//self.helpObject 就是一个 UITextField
//[self addTarget:self.helper action:@selector(textFiledEditChanged) forControlEvents:UIControlEventEditingChanged];
- (void)textFiledEditChanged
{
if (self.helpObject.maxLength != 0) {
NSInteger kmaxLength = self.helpObject.maxLength;
NSString *toBeString = self.helpObject.text;
//截取长度
if (toBeString.length > kmaxLength) {
self.helpObject.text = [toBeString substringToIndex:kmaxLength];
}
}
}
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
//长度校验 string.length > 0 排除删除按
if (self.helpObject.maxLength != 0 && string.length != 0) {
NSString *toBeString = [textField.text stringByReplacingCharactersInRange:range withString:string];
NSInteger kmaxLength = self.helpObject.maxLength;
if (toBeString.length > kmaxLength){
return NO;
}
}
return YES;
}
二 限制emoji表情
还是网上一搜索 搜到的都不合理只好自己捣鼓。直接上反例。
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
//检查 string string 包含emoji表情则return NO;
}
上诉代码解决不了通过文字联想出来的emoji表情输入,因为这个方法更本监听不到。
其次上述代码非常隐晦的屏蔽了九宫格输入法。因为点击九宫格按键得到的string就是emoji表情符号文字 ①②③④⑤⑥⑦⑧⑨⑩
解决方案
想到用UIControlEventEditingChanged事件,但是不可取,因为这个事件并不知道emoji表情添加到哪个位置上,如果每次在此方法中做个遍历,去掉emoji太耗性能。所以采用
- (void)setMarkedText:(NSString *)markedText selectedRange:(NSRange)selectedRange
这个方法,通过联想得到的emoji表情,选取后必然调用这个,我们只要重写此方法,就能做到过滤。在
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
方法中加入校验,当前是emoji表情键盘的时候才进行验证,==此处有坑,网上找的方法全都是采用if ([[[UITextInputMode currentInputMode ]primaryLanguage] isEqualToString:@"emoji"])
currentInputMode 这明明都是IOS7就废弃的方法,好吧那就采用 textField.textInputMode.primaryLanguage 结果选择emoji表情的时候得到的是nil 直接判断nil为moeji表情键盘太不合理了==,网上找了半天没找到,然后自己瞎捣鼓了下发现了[UITextInputMode activeInputModes]
这里有然后就写了个方法 不过得到的数组实际都是iOS的私有类,不过根据泛型参数估且认为他是NSString *类型然后通过KVC可以取得我们想要的结果。
Demo
- (BOOL)nowIsEmojiKeyBorad
{
for (NSString *keyboardInputMode in [UITextInputMode activeInputModes]) {
if ([[keyboardInputMode valueForKey:PrimaryLanguage] isEqualToString:EmojiprimaryLanguage]) {
//这里ios7会崩溃的建议增加判断 本文不做展开 具体参见博客
NSNumber *isDisplayed = [keyboardInputMode valueForKey:IsDisplayed];
if ([isDisplayed boolValue] == YES) {
return YES;
}
}
}
return false;
}
- (void)setMarkedText:(NSString *)markedText selectedRange:(NSRange)selectedRange
{
//将含有emoji表情的文字替换为@""
NSString *helpMarkedText = [self.helper setMarkedText:markedText selectedRange:selectedRange];
[super setMarkedText:helpMarkedText selectedRange:selectedRange];
}
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
//判断是否是 emoji键盘 并且 string.length > 0 (排除删除按)
//检查 string string 包含emoji表情则return NO;
}
==写到这里,具体控件实现,设计思路放下一篇博客里讲==