UITextField限制最大输入字符数

在开发中我们经常遇到这样的需求:在UITextField或者UITextView中限制用户可以输入的最大字符数。如果是纯英文的输入,很好解决。但是遇到中文输入法,就会遇到各种坑,而且iOS系统自带的中文输入法和第三方输入法(搜狗,百度)也要区别对待,emoji表情也是个大坑,搞不好就截取错误,导致emoji表情显示错误。

下面我们来看看如何填坑。


纯英文

刚开始我是这样处理的,代码如下

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
    // Prevent crashing undo bug – see note below.
    if(range.length + range.location > textField.text.length)
    {
        return NO;
    }

    NSUInteger newLength = [textField.text length] + [string length] - range.length;
    return newLength <= 25;
}

这个方法是UITextField的代理方法,作用如下: 询问代理在range范围内的文本是否需要替换为replacementString,Yes就替换,反之就不替换 具体的参考下面这个链接: http://stackoverflow.com/questions/433337/set-the-maximum-character-length-of-a-uitextfield

但是这个方法只能处理纯英文的输入,碰到中文输入法,就没法判断了。 具体原因我们下面分析


系统自带的中文输入法

使用系统中文输入法的时候,会出现如下的情况,如图所示: image

我们可以看到在没有按确认键之前,你输入的任何汉字只是在输入法的上面显示出来,在输入框中被灰色遮盖的部分只是显示你输入的字母,直到你按确认键之后,输入法上面的汉字才会替换输入框中的被遮盖的字母。

问题就出在输入框中被遮盖的部分(我们暂且称之为高亮部分,后面都是这样),因为使用上面的方法计算输入框中字符数所占据的range,英文一个字母就是1,这个时候统计是没有问题的。

但是遇到上图所示的情况,这个方法对高亮部分的统计是有问题的,我不知道苹果内部是如何计算高亮部分所占据的range,完全没有规律可循。不信大家可以自行打印一下range参数。假设我们限制最大只能输入10个字符,我们使用中文输入法的时候,大概在输入框中输入5到6个字符(不是固定不变的,根据输入的汉字不同而不同)就不让我们继续输入了,因为高亮部分已经占据了10个字符了,虽然我们看到的高亮部分只有5,6个字符。

问题我们已经找出来了,下面我们看如何解决

@interface ViewController ()
@property(strong,nonatomic)UITextField *textField;
@property(assign,nonatomic)NSInteger maxCount;

@end

@implementation ViewController

- (void)viewDidLoad
{
    //监听UITextFieldTextDidChangeNotification通知,可以在UITextField发生变化的时候接收到通知
    [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(textFiledEditChanged) name:@"UITextFieldTextDidChangeNotification" object:nil];

    self.textField = [[UITextField alloc]initWithFrame:CGRectMake(100, 200, 200, 44)];
    self.textField.backgroundColor = [UIColor redColor];
    [self.view addSubview:self.textField];
    self.maxCount = 10;

}

//实现监听方法
-(void)textFiledEditChanged{   
   NSString *toBeString = self.textField.text;  
   NSString *lang = [[UITextInputMode currentInputMode] primaryLanguage]; // 键盘输入模式

   if ([lang isEqualToString:@"zh-Hans"]) { // 简体中文输入,包括简体拼音,健体五笔,简体手写       
      UITextRange *selectedRange = [self.textField markedTextRange];       //获取高亮部分的range

      //获取高亮部分的从range.start位置开始,向右偏移0所得的字符所在的位置。如果高亮部分不存在,那么就返回nil,反之就不是nil    
      UITextPosition *position = [self.textField positionFromPosition:selectedRange.start offset:0];       

      // 没有高亮选择的字,则对已输入的文字进行字数统计和限制       
      if (!position) {
           if (toBeString.length > self.maxCount) {
               self.textField.text = [toBeString substringToIndex:self.maxCount];
           }
       }       
       // 有高亮选择的字符串,则暂不对文字进行统计和限制
       else{                
        }   
      }   

      // 中文输入法以外的直接对其统计限制即可 
       else{
       if (toBeString.length > self.maxCount) {
           self.textField.text = [toBeString substringToIndex:self.maxCount];
       }
   }}

这个时候我们在输入中文输入法,发现没有问题了。

就在我们以为大功告成的时候,手贱点了一下emoji表情,然后就出现下面的问题了: image

输入到第九个汉字的时候,我输入了一个emoji表情,然后就悲剧了,表情显示不完整 image

不过,既然问题出现了,我们还是来看看如何解决吧


系统中文输入法emoji表情截取错误

出现上面这个问题的原因是:emoji表情也是使用字符来表示的,不过一般最少是2个字符表示,或者4个,6个来表示,不同的输入法不相同。

我们上面的方法就是粗暴的截取输入框中前10个字符,那么第九个汉字加上2个字符表示的表情就是11个字符了,这个时候emoji表情只被截取了前一个字符,后面一个字符没有显示出来,然后就悲剧了。

那么就解决办法就是,当我们输入emoji表情的时候,需要做判断。

我们假设:

新的最大字符数 = 输入框中的字符 + emoji表情字符。

那么:

如果,新的最大字符数 <= 原始限制的最大输入字符数,还是和之前的处理方法类似

如果,新的最大字符数 > 原始限制的最大输入字符数,就设置:原始的限制输入的最大字符数 = 新的最大字符数。

问题就迎刃而解了。

这里需要用到NSString类中的两个方法:

  • rangeOfComposedCharacterSequenceAtIndex
  • rangeOfComposedCharacterSequencesForRange

下面来看看这两个方法到底干嘛用的,来看个小例子

NSString *str =  @"😄你好s🚣🏻🚜🔯🇮🇪s😄s";

    NSRange rangeIndex = [str rangeOfComposedCharacterSequenceAtIndex:5];
    NSString *string = [str substringWithRange:rangeIndex];

下面是四种情况: image image image image

可以看到这个方法的作用就是从rangeOfComposedCharacterSequenceAtIndex:<#(NSUInteger)#>的参数NSUInteger位置处,向后计算一个完整字符串所占据的range。

这不正是我们想要的效果吗?

同理rangeOfComposedCharacterSequencesForRange:<#(NSRange)#>方法就是返回参数range范围内完整字符串所占据的新的range。

有点拗口,看具体的例子:

image

可以看到虽然我们的设置的range是(0,6),刚好是字符‘s’之后的船的一个字符,这个时候该方法返回来的range是(0,9),正好包括了整个船的字符。最后显示出来的字符也是完整的一只小船。

两个方法讲完了,我们来看看如何使用这两个方法来处理我们的问题,直接上代码

-(void)textFiledEditChanged{
    NSString *toBeString = self.textField.text;
    NSString *lang = [self.textField.textInputMode primaryLanguage];
    if ([lang isEqualToString:@"zh-Hans"])// 简体中文输入
    {
        //获取高亮部分
        UITextRange *selectedRange = [self.textField markedTextRange];
        UITextPosition *position = [self.textField positionFromPosition:selectedRange.start offset:0];

        // 没有高亮选择的字,则对已输入的文字进行字数统计和限制
        if (!position)
        {
            if (toBeString.length > self.maxCount) {
            self.textField.text = [toBeString substringToIndex:self.maxCount];
           }
       } 

       // 有高亮选择的字符串,则暂不对文字进行统计和限制
       else{                
        }   
    }


    // 中文输入法以外(英文和emoji)的直接对其统计限制即可
    else
    {
        if (toBeString.length > self.maxCount)
        {
            NSRange rangeIndex = [toBeString rangeOfComposedCharacterSequenceAtIndex:self.maxCount];

            //如果是汉字,就直接截取到限制的最大字符数
            if (rangeIndex.length == 1)
            {
                self.textField.text = [toBeString substringToIndex:self.maxCount];
            }

            //如果不是汉字,那就是emoji表情了,就截取到包括完整emoji表情后的range范围的字符
            else
            {
                NSRange rangeRange = [toBeString rangeOfComposedCharacterSequencesForRange:NSMakeRange(0, self.maxCount)];
                self.textField.text = [toBeString substringWithRange:rangeRange];
            }
        }
    }

}

再来运行下,发现简直完美,按捺住内心的小激动。然后试了下第三方输入法搜狗和百度,输入到emoji表情的时候,又出现emoji表情截取错误。。。 image

我赵日天不服啊,继续解决bug


第三方中文输入法emoji表情截取错误

其实想了下,很好解决,复制黏贴代码就可以了。我们在使用中文输入法的时候也做一下判断嘛。

代码如下:

-(void)textFiledEditChanged{
    NSString *toBeString = self.textField.text;
    NSString *lang = [self.textField.textInputMode primaryLanguage];
    if ([lang isEqualToString:@"zh-Hans"])// 简体中文输入
    {
        //获取高亮部分
        UITextRange *selectedRange = [self.textField markedTextRange];
        UITextPosition *position = [self.textField positionFromPosition:selectedRange.start offset:0];

        // 没有高亮选择的字,则对已输入的文字进行字数统计和限制
        if (!position)
        {
            if (toBeString.length > self.maxCount)
            {
            //判断第三方中文输入法的emoji表情
                NSRange rangeIndex = [toBeString rangeOfComposedCharacterSequenceAtIndex:self.maxCount];
                if (rangeIndex.length == 1)
                {
                    self.textField.text = [toBeString substringToIndex:self.maxCount];
                }
                else
                {
                    NSRange rangeRange = [toBeString rangeOfComposedCharacterSequencesForRange:NSMakeRange(0, self.maxCount)];
                    self.textField.text = [toBeString substringWithRange:rangeRange];
                }
            }
        }
    }

    // 中文输入法以外(英文和emoji)的直接对其统计限制即可
    else
    {
        if (toBeString.length > self.maxCount)
        {
            NSRange rangeIndex = [toBeString rangeOfComposedCharacterSequenceAtIndex:self.maxCount];
            if (rangeIndex.length == 1)
            {
                self.textField.text = [toBeString substringToIndex:self.maxCount];
            }
            else
            {
                NSRange rangeRange = [toBeString rangeOfComposedCharacterSequencesForRange:NSMakeRange(0, self.maxCount)];
                self.textField.text = [toBeString substringWithRange:rangeRange];
            }
        }
    }

}

运行下

image

PS:
上面的方法,还不能对付颜文字,火星文之类的,还是会出现截取错误~

comments powered by Disqus