文档更新说明
- 最后更新 2017年04月11日
- 首次更新 2017年04月7日
前言
近期在项目中处理了不少和UITextField相关的需求,例如监听输入内容变化,代码修改TextField内容,限制长度,限制emoji表情输入等,在这里总结一下相关方法,给出一些意料之外的问题的解决方案.
事件监听与触发条件
使用UITextFieldDelegate监听键盘输入
常见的检测输入变化的代理应该是:
1 2 3 4 5 6 7 8 9
| - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { NSLog(@"~"); return YES; }
- (void)textFieldDidEndEditing:(UITextField *)textField { NSLog(@"~"); }
|
重点提醒:代理无法监听到非键盘输入引起的变化,例如Coding引起的
使用控件Event监听
常用方法如下:
1
| [textField addTarget:self action:@selector(didTextChange:) forControlEvents:UIControlEventEditingChanged];
|
重点提醒:Event无法监听到非键盘输入引起的变化,例如Coding引起的
使用NSNotificationCenter监听键盘输入
常用的监听事件可以是UITextFieldTextDidChangeNotification,UITextFieldTextDidEndEditingNotification.作用同代理一样,区别就是键盘输入引起的变化只是作为变化之后的通知,不具备控制”是否允许变化”这种功能.
1 2
| [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didTextChange:) name:UITextFieldTextDidChangeNotification object:self.textField]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didTextEndEditing:) name:UITextFieldTextDidEndEditingNotification object:self.textField];
|
重点提醒: NSNotificationCenter也无法监听到非键盘输入引起的变化,例如Coding引起的
使用KVO监听Coding引起的变化
常用的方法可以是下面这样:
1 2 3 4 5 6 7 8
| [self.textField addObserver:self forKeyPath:@"text" options:NSKeyValueObservingOptionNew context:nil];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqualToString:@"text"]) { } }
|
重点提醒:上面三种方法都只能监听键盘引起的变化,而KVO则无法监听键盘引起的变化,但是可以监听代码引起的变化.
知道了如何监听各种情况的输入变化之后,我们就可以想出办法来做统一监听了.
简单说描述一下,例如我们可以实现一个UITextField的子类CCTextField,在CCTextField中分别监听键盘输入+代码输入两种情况,再利用NSNotificationCenter发出文本变化通知,以后自己可以注册CCTextField的kCCTextFieldNotificationTextDidChange通知得到统一监听.
1
| [[NSNotificationCenter defaultCenter] postNotificationName:kCCTextFieldNotificationTextDidChange object:self];
|
输入内容限制
限制输入的目的就是要限制用户输入过多的内容或者不合法的内容.很容易想到的就是在用户输入不符合条件的内容时,利用代理方法shouldChangeCharactersInRange直接取消输入的内容.当然实际使用会遇到意料之外的问题,下面一一分析.
限制内容长度
最常见的需求就是限制输入长度,处理方法如下:
1 2 3 4 5 6
| - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { if (textField.text.length + string.length - range.length > limitLength) { return NO; } return YES; }
|
限制输入中文长度
限制输入中文长度应该和预想的有些不一样,这是由于一些输入法(系统输入法)会随着输入拼音的同时将拼音键入输入框,从而导致相关监听方法被触发,最终计算出一个错误的长度,因此优化之后的代码应该如下:
1 2 3 4 5 6 7
| - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { if (![textField markedTextRange] && string.length != 0 && textField.text.length + string.length - range.length > limitLength) { return NO; } return YES; }
|
另一种限制长度的方案
上面的方案是在输入还未成效的时候决定是否放行,下面的方法则是输入完成之后检测长度并做限制.
1 2 3 4 5 6
| - (void)didTextChange:(UITextField *)sender { if (![sender markedTextRange] && sender.text.length > maxLength) { sender.text = [sender.text substringWithRange:NSMakeRange(0, maxLength)]; } }
|
限制输入内容类型
常见的需求可能是限制一些特殊符号的输入,例如emoji表情,处理方法同理可通过shouldChangeCharactersInRange方法检测新增内容是否包含emoji,再决定是否允许输入.
封装限制代码
不同的业务逻辑一般在处理输入限制的同时,还需要处理其他需求.比如注册界面,除了限制输入内容之外还需要检测两次代码是否一致,当不一致时要在界面上给出UI提示等.所以封装采用的方案最好是能轻松地融入实际业务逻辑,例如采用OC中的Category来封装.先看下面实际代码,再做说明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
|
#import <UIKit/UIKit.h>
@interface UITextField (CCLimit)
- (BOOL)ccDecimals2_shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string limitLength:(NSInteger)limitLength;
- (BOOL)ccInteger_shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string limitLength:(NSInteger)limitLength;
- (BOOL)ccMaxNumber_shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string maxNumber:(double)max;
- (BOOL)ccLength_shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string limitLength:(NSInteger)limitLength;
- (NSInteger)ccWholeLength_changeCharactersInRange:(NSRange)range replacementString:(NSString *)string; @end
#import "UITextField+CCLimit.h" #import "NSString+Extra.h" #import "NSString+Emoji.h"
@implementation UITextField (CCLimit)
- (BOOL)ccDecimals2_shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string limitLength:(NSInteger)limitLength {
NSCharacterSet *set; set = [[NSCharacterSet characterSetWithCharactersInString:@"0123456789."] invertedSet]; if ([string rangeOfCharacterFromSet:set].location != NSNotFound) { return NO; }else if (self.text.length == 0 && [string hasPrefix:@"."]) { return NO; }else { NSString *deletedStr = [self.text stringByReplacingCharactersInRange:range withString:@""]; NSString *wholeStr = [NSString stringWithFormat:@"%@%@", deletedStr, string]; NSArray *strComAry = [wholeStr componentsSeparatedByString:@"."]; if (wholeStr.length > limitLength) { return NO; }else if ([wholeStr countOccurencesOfString:@"."] > 1){ return NO; }else if (strComAry.count == 2){ NSString *decimalString = strComAry[1]; if (decimalString.length > 2) { return NO; } } } return YES; }
- (BOOL)ccMaxNumber_shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string maxNumber:(double)max {
NSCharacterSet *set; set = [[NSCharacterSet characterSetWithCharactersInString:@"0123456789."] invertedSet]; if ([string rangeOfCharacterFromSet:set].location != NSNotFound) { return NO; }else { NSString *deletedStr = [self.text stringByReplacingCharactersInRange:range withString:@""]; NSString *wholeStr = [NSString stringWithFormat:@"%@%@", deletedStr, string]; if ([wholeStr doubleValue] > max) { return NO; } } return YES; }
- (BOOL)ccInteger_shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string limitLength:(NSInteger)limitLength {
NSCharacterSet *set = [NSCharacterSet decimalDigitCharacterSet]; if (string.length != 0 && [string rangeOfCharacterFromSet:set].location == NSNotFound) { return NO; } if ([self ccWholeLength_changeCharactersInRange:range replacementString:string] > limitLength) { return NO; } return YES; }
- (BOOL)ccLength_shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string limitLength:(NSInteger)limitLength { if ([self ccWholeLength_changeCharactersInRange:range replacementString:string] > limitLength) { return NO; } return YES; }
- (NSInteger)ccWholeLength_changeCharactersInRange:(NSRange)range replacementString:(NSString *)string { return self.text.length + string.length - range.length; } @end
|
上面分别是对我们公司项目某些常用需求的封装,使用方法也很简单,在对应的代理方法内调用Category里面的方法进行处理就可以了.即可以单独使用,也方便在具体代理方法中使用Category的方法的同时加上自己的其他需求(例如下面例子,结合实际需求同时处理了两个输入框).另外封装的不同功能也可以组合起来使用,看下面例子.
1 2 3 4 5 6 7 8 9 10 11 12
|
#pragma mark - UITextFieldDelegate - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { if (textField == self.amountDUView.textField) { return [self.amountDUView.textField ccInteger_shouldChangeCharactersInRange:range replacementString:string limitLength:5]; }else if (textField == self.priceDUView.textField) { return [self.priceDUView.textField ccDecimals2_shouldChangeCharactersInRange:range replacementString:string limitLength:4] && [self.priceDUView.textField ccMaxNumber_shouldChangeCharactersInRange:range replacementString:string maxNumber:1]; } return NO; }
|
处理emoji表情
有些输入要求禁用emoji表情,我们要做的核心功能就是能检查到输入了emoji表情了,剩下都好办.这里推荐一篇文章iOS过滤全局的Emoji表情输入
总结
本文主要描述了UITextField输入限制的思路,并且给出了常用的输入监听方法,限制封装方法和Emoji表情👀的过滤方法.在针对”限制封装”部分,给出的例子只是我个人工作项目中常遇到的,不具有一般性,所以读者要注意理解用Category设计的思路和原因,至于具体例子只是为了方面描述问题而已;另外关于”全局过滤Emoji”一文,里面的作者所讲述的方案我已经用在项目中了,是一个比较完美的方案,推荐大家学习.
而关于UITextView思路大同小异,具体实现略有不同,就不多说了.