限制UITextField的最大輸入字符數(shù)

在開發(fā)中我們經(jīng)常遇到這樣的需求:在UITextField或者UITextView中限制用戶可以輸入的最大字符數(shù)。如果是純英文的輸入,很好解決。但是遇到中文輸入法,就會(huì)遇到各種坑,而且iOS系統(tǒng)自帶的中文輸入法和第三方輸入法(搜狗,百度)也要區(qū)別對(duì)待,emoji表情也是個(gè)大坑,搞不好就截取錯(cuò)誤,導(dǎo)致emoji表情顯示錯(cuò)誤。

下面我們來(lái)看看如何填坑。

1.純英文

剛開始我是這樣處理的,代碼如下

- (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;
}

這個(gè)方法是UITextField的代理方法,作用如下:
詢問代理在range范圍內(nèi)的文本是否需要替換為replacementString,Yes就替換,反之就不替換
具體的參考下面這個(gè)鏈接:
http://stackoverflow.com/questions/433337/set-the-maximum-character-length-of-a-uitextfield

但是這個(gè)方法只能處理純英文的輸入,碰到中文輸入法,就沒法判斷了。
具體原因我們下面分析

2.系統(tǒng)自帶的中文輸入法

使用系統(tǒng)中文輸入法的時(shí)候,會(huì)出現(xiàn)如下的情況,如圖所示:


image

我們可以看到在沒有按確認(rèn)鍵之前,你輸入的任何漢字只是在輸入法的上面顯示出來(lái),在輸入框中被灰色遮蓋的部分只是顯示你輸入的字母,直到你按確認(rèn)鍵之后,輸入法上面的漢字才會(huì)替換輸入框中的被遮蓋的字母。

問題就出在輸入框中被遮蓋的部分(我們暫且稱之為高亮部分,后面都是這樣),因?yàn)槭褂蒙厦娴姆椒ㄓ?jì)算輸入框中字符數(shù)所占據(jù)的range,英文一個(gè)字母就是1,這個(gè)時(shí)候統(tǒng)計(jì)是沒有問題的。

但是遇到上圖所示的情況,這個(gè)方法對(duì)高亮部分的統(tǒng)計(jì)是有問題的,我不知道蘋果內(nèi)部是如何計(jì)算高亮部分所占據(jù)的range,完全沒有規(guī)律可循。不信大家可以自行打印一下range參數(shù)。假設(shè)我們限制最大只能輸入10個(gè)字符,我們使用中文輸入法的時(shí)候,大概在輸入框中輸入5到6個(gè)字符(不是固定不變的,根據(jù)輸入的漢字不同而不同)就不讓我們繼續(xù)輸入了,因?yàn)楦吡敛糠忠呀?jīng)占據(jù)了10個(gè)字符了,雖然我們看到的高亮部分只有5,6個(gè)字符。

問題我們已經(jīng)找出來(lái)了,下面我們看如何解決

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

@end

@implementation ViewController

- (void)viewDidLoad
{
    //監(jiān)聽UITextFieldTextDidChangeNotification通知,可以在UITextField發(fā)生變化的時(shí)候接收到通知
    [[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;

}

//實(shí)現(xiàn)監(jiān)聽方法
-(void)textFiledEditChanged{   
   NSString *toBeString = self.textField.text;  
   NSString *lang = [[UITextInputMode currentInputMode] primaryLanguage]; // 鍵盤輸入模式
     
   if ([lang isEqualToString:@"zh-Hans"]) { // 簡(jiǎn)體中文輸入,包括簡(jiǎn)體拼音,健體五筆,簡(jiǎn)體手寫       
      UITextRange *selectedRange = [self.textField markedTextRange];       //獲取高亮部分的range
      
      //獲取高亮部分的從range.start位置開始,向右偏移0所得的字符所在的位置。如果高亮部分不存在,那么就返回nil,反之就不是nil    
      UITextPosition *position = [self.textField positionFromPosition:selectedRange.start offset:0];       
      
      // 沒有高亮選擇的字,則對(duì)已輸入的文字進(jìn)行字?jǐn)?shù)統(tǒng)計(jì)和限制       
      if (!position) {
           if (toBeString.length > self.maxCount) {
               self.textField.text = [toBeString substringToIndex:self.maxCount];
           }
       }       
       // 有高亮選擇的字符串,則暫不對(duì)文字進(jìn)行統(tǒng)計(jì)和限制
       else{                
        }   
      }   
      
      // 中文輸入法以外的直接對(duì)其統(tǒng)計(jì)限制即可 
       else{
       if (toBeString.length > self.maxCount) {
           self.textField.text = [toBeString substringToIndex:self.maxCount];
       }
   }}

這個(gè)時(shí)候我們?cè)谳斎胫形妮斎敕?,發(fā)現(xiàn)沒有問題了。

就在我們以為大功告成的時(shí)候,手賤點(diǎn)了一下emoji表情,然后就出現(xiàn)下面的問題了:


image

輸入到第九個(gè)漢字的時(shí)候,我輸入了一個(gè)emoji表情,然后就悲劇了,表情顯示不完整


image

不過,既然問題出現(xiàn)了,我們還是來(lái)看看如何解決吧

系統(tǒng)中文輸入法emoji表情截取錯(cuò)誤

出現(xiàn)上面這個(gè)問題的原因是:emoji表情也是使用字符來(lái)表示的,不過一般最少是2個(gè)字符表示,或者4個(gè),6個(gè)來(lái)表示,不同的輸入法不相同。

我們上面的方法就是粗暴的截取輸入框中前10個(gè)字符,那么第九個(gè)漢字加上2個(gè)字符表示的表情就是11個(gè)字符了,這個(gè)時(shí)候emoji表情只被截取了前一個(gè)字符,后面一個(gè)字符沒有顯示出來(lái),然后就悲劇了。

那么就解決辦法就是,當(dāng)我們輸入emoji表情的時(shí)候,需要做判斷。

我們假設(shè):

新的最大字符數(shù) = 輸入框中的字符 + emoji表情字符。

那么:

如果,新的最大字符數(shù) <= 原始限制的最大輸入字符數(shù),還是和之前的處理方法類似

如果,新的最大字符數(shù) > 原始限制的最大輸入字符數(shù),就設(shè)置:原始的限制輸入的最大字符數(shù) = 新的最大字符數(shù)。

問題就迎刃而解了。

這里需要用到NSString類中的兩個(gè)方法:

  • rangeOfComposedCharacterSequenceAtIndex
  • rangeOfComposedCharacterSequencesForRange

下面來(lái)看看這兩個(gè)方法到底干嘛用的,來(lái)看個(gè)小例子

NSString *str =  @"??你好s????????????s??s";
    
    NSRange rangeIndex = [str rangeOfComposedCharacterSequenceAtIndex:5];
    NSString *string = [str substringWithRange:rangeIndex];


下面是四種情況:


image

image

image

image

可以看到這個(gè)方法的作用就是從rangeOfComposedCharacterSequenceAtIndex:<#(NSUInteger)#>的參數(shù)NSUInteger位置處,向后計(jì)算一個(gè)完整字符串所占據(jù)的range。

這不正是我們想要的效果嗎?

同理rangeOfComposedCharacterSequencesForRange:<#(NSRange)#>方法就是返回參數(shù)range范圍內(nèi)完整字符串所占據(jù)的新的range。

有點(diǎn)拗口,看具體的例子:

image

可以看到雖然我們的設(shè)置的range是(0,6),剛好是字符‘s’之后的船的一個(gè)字符,這個(gè)時(shí)候該方法返回來(lái)的range是(0,9),正好包括了整個(gè)船的字符。最后顯示出來(lái)的字符也是完整的一只小船。

兩個(gè)方法講完了,我們來(lái)看看如何使用這兩個(gè)方法來(lái)處理我們的問題,直接上代碼

-(void)textFiledEditChanged{
    NSString *toBeString = self.textField.text;
    NSString *lang = [self.textField.textInputMode primaryLanguage];
    if ([lang isEqualToString:@"zh-Hans"])// 簡(jiǎn)體中文輸入
    {
        //獲取高亮部分
        UITextRange *selectedRange = [self.textField markedTextRange];
        UITextPosition *position = [self.textField positionFromPosition:selectedRange.start offset:0];
    
        // 沒有高亮選擇的字,則對(duì)已輸入的文字進(jìn)行字?jǐn)?shù)統(tǒng)計(jì)和限制
        if (!position)
        {
            if (toBeString.length > self.maxCount) {
            self.textField.text = [toBeString substringToIndex:self.maxCount];
           }
       } 
             
       // 有高亮選擇的字符串,則暫不對(duì)文字進(jìn)行統(tǒng)計(jì)和限制
       else{                
        }   
    }
    
    
    // 中文輸入法以外(英文和emoji)的直接對(duì)其統(tǒng)計(jì)限制即可
    else
    {
        if (toBeString.length > self.maxCount)
        {
            NSRange rangeIndex = [toBeString rangeOfComposedCharacterSequenceAtIndex:self.maxCount];
            
            //如果是漢字,就直接截取到限制的最大字符數(shù)
            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];
            }
        }
    }

}

再來(lái)運(yùn)行下,發(fā)現(xiàn)簡(jiǎn)直完美,按捺住內(nèi)心的小激動(dòng)。然后試了下第三方輸入法搜狗和百度,輸入到emoji表情的時(shí)候,又出現(xiàn)emoji表情截取錯(cuò)誤。。。


image

我趙日天不服啊,繼續(xù)解決bug

第三方中文輸入法emoji表情截取錯(cuò)誤

其實(shí)想了下,很好解決,復(fù)制黏貼代碼就可以了。我們?cè)谑褂弥形妮斎敕ǖ臅r(shí)候也做一下判斷嘛。

代碼如下:

-(void)textFiledEditChanged{
    NSString *toBeString = self.textField.text;
    NSString *lang = [self.textField.textInputMode primaryLanguage];
    if ([lang isEqualToString:@"zh-Hans"])// 簡(jiǎn)體中文輸入
    {
        //獲取高亮部分
        UITextRange *selectedRange = [self.textField markedTextRange];
        UITextPosition *position = [self.textField positionFromPosition:selectedRange.start offset:0];
    
        // 沒有高亮選擇的字,則對(duì)已輸入的文字進(jìn)行字?jǐn)?shù)統(tǒng)計(jì)和限制
        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)的直接對(duì)其統(tǒng)計(jì)限制即可
    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];
            }
        }
    }

}

運(yùn)行下

image

PS:
上面的方法,還不能對(duì)付顏文字,火星文之類的,還是會(huì)出現(xiàn)截取錯(cuò)誤~

更多技術(shù)文章歡迎大家訪問我的個(gè)人博客:Ximu&Moliang's Blog

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容