文章目录
  1. 1. 文档更新说明
  2. 2. 前言
  3. 3. 事件监听与触发条件
    1. 3.1. 使用UITextFieldDelegate监听键盘输入
    2. 3.2. 使用控件Event监听
    3. 3.3. 使用NSNotificationCenter监听键盘输入
    4. 3.4. 使用KVO监听Coding引起的变化
  4. 4. 输入内容限制
    1. 4.1. 限制内容长度
      1. 4.1.1. 限制输入中文长度
    2. 4.2. 另一种限制长度的方案
    3. 4.3. 限制输入内容类型
  5. 5. 封装限制代码
  6. 6. 处理emoji表情
  7. 7. 总结

文档更新说明

  • 最后更新 2017年04月11日
  • 首次更新 2017年04月7日

前言

  近期在项目中处理了不少和UITextField相关的需求,例如监听输入内容变化,代码修改TextField内容,限制长度,限制emoji表情输入等,在这里总结一下相关方法,给出一些意料之外的问题的解决方案.

事件监听与触发条件

使用UITextFieldDelegate监听键盘输入

  常见的检测输入变化的代理应该是:

1
2
3
4
5
6
7
8
9
//可以监听键盘输入到TextField的内容,并返回一个BOOL来表示是否允许本次输入
- (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"]) {
//do something
}
}

重点提醒:上面三种方法都只能监听键盘引起的变化,而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 {
//[textField markedTextRange]用于判断当前输入内容是否处于选择状态,是则表示未完成中文输入,输入的只是拼音
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
//
// FMPriceTextField+Decimals2.h
//
// Created by Cocos on 2016/11/22.
// Copyright © 2016年 Cocos. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface UITextField (CCLimit)
//只允许输入小数点2位数的小数类型内容,可以限制字符的总长度
- (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

//
// FMPriceTextField+Decimals2.m
//
// Created by Cocos on 2016/11/22.
// Copyright © 2016年 Cocos. All rights reserved.
//

#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:@"."]) {
//Start with "." when the textField.text.length is 0
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
//在某个视图里面实现UITextField的代理
//当前视图一共代理了两个UITextField,数量和价格,所以在UITextField代理方法里面,分别处理了数量和价格的限制,其中处理价格的时候又将封装的功能组合起来使用,分别是限制小数点2位数和数字最大值
#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思路大同小异,具体实现略有不同,就不多说了.         

文章目录
  1. 1. 文档更新说明
  2. 2. 前言
  3. 3. 事件监听与触发条件
    1. 3.1. 使用UITextFieldDelegate监听键盘输入
    2. 3.2. 使用控件Event监听
    3. 3.3. 使用NSNotificationCenter监听键盘输入
    4. 3.4. 使用KVO监听Coding引起的变化
  4. 4. 输入内容限制
    1. 4.1. 限制内容长度
      1. 4.1.1. 限制输入中文长度
    2. 4.2. 另一种限制长度的方案
    3. 4.3. 限制输入内容类型
  5. 5. 封装限制代码
  6. 6. 处理emoji表情
  7. 7. 总结