UITextField輸入字數限制

一開始,我以為這是一個屬性設置問題或者是一個類似helloworld的函數,但是最后發現,這呀的是一個不小的坑。

1、哪些問題需要考慮?

  • 輸入字數有哪些?數字、英文、中文、emoji?
  • 字數限制怎么定義?一個中文/emoji算一個字?
  • 如果限制的是字節數呢?
  • 會出現字符截斷問題?
  • (中文輸入法)在剩余的輸入字數為1的情況下,是否還允許用戶輸入一堆拼音字符?
  • 要是粘貼進一大段文字,能正確處理?
  • ...

2、測試用例

在介紹用例之前,我們先來看看微信(iOS V6.5.12.32)是怎么做的。進入微信的名字修改界面~

  • 純數字輸入,允許輸入32個數字。
  • 純emoji輸入,允許輸入8個emoji。
  • 純中文輸入,允許輸入16個中文
  • 先輸入15個中文,然后再輸入一個中文。是的,此時你只能輸入一個拼音字母(也即前面的“h”問題)。
  • 存在上述提到的黏貼問題
    基本可以斷定微信的名字是用字節數做了限制,并且使用的是3.1提到的方法。

下面是具體的測試用例(最基本的用例就不贅述了):(如無特殊說明,限定輸入字數為10個)
case1:輸入10個中文,此時是否還允許有高亮的拼音字母輸入?
case2:輸入10個中文,將鍵盤切換為九宮格英文輸入模式,快速點擊某個字母,最后一個中文是否會被替換?
case3:在剩余的輸入字數為1的情況下,是否還允許用戶輸入一堆高亮拼音字符?
case4:輸入字數已經滿了,此時是否允許粘貼?????
case5:限制輸入字符為11個,輸入10個中文,再輸入一個emoji表情,emoji會被截斷?

3、網上參考做法

網上很多博客介紹了相關的方法,但是歸根到底就是兩種:
3.1 實現 UITextFieldDelegate 協議中的接口,這里有說明

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

該方法最簡單,在只允許輸入數字/英文/emoji的情況下,基本能滿足。但該方法在黏貼文本長度超過允許值時,直接黏貼失敗(也許我們更期待是自動截取)。
在允許輸入中文的情況,該方法的缺陷更明顯。假如還剩下一個字可以輸入,你打算輸入“好”,當你輸入字母“h”之后,發現無法再輸入其他拼音字母了。

3.2 為UITextField實例添加監聽 UIControlEventEditingChanged,然后對其進一步處理。可以通過在NSNotificationCenter中添加監聽,但是還需手動移除,比較繁瑣,所以推薦直接使用下面的方法

[textField addTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged];

- (void)textFieldDidChange:(UITextField *)textField
{
    NSString *toBeString = textField.text;
    
    //獲取高亮部分
    UITextRange *selectedRange = [textField markedTextRange];
    UITextPosition *position = [textField positionFromPosition:selectedRange.start offset:0];
    
    // 沒有高亮選擇的字,則對已輸入的文字進行字數統計和限制
    if (!position)
    {
        if (toBeString.length > kMaxLength)
        {
            NSRange rangeIndex = [toBeString rangeOfComposedCharacterSequenceAtIndex:kMaxLength];
            if (rangeIndex.length == 1)
            {
                textField.text = [toBeString substringToIndex:kMaxLength];
            }
            else
            {
                NSRange rangeRange = [toBeString rangeOfComposedCharacterSequencesForRange:NSMakeRange(0, kMaxLength - 1 )];
                textField.text = [toBeString substringWithRange:rangeRange];
            }
        }
    }
}

該方法是對3.1方法的改善,但是仍舊存在不足。
其一,假如限制輸入5個中文,你可以嘗試在輸入5個中文字符之后,將鍵盤切換為九宮格英文輸入模式,快速點擊某個字母,看看最后一個中文是否會被替換?
再者,當輸入達到限制的字數時,仍舊可以輸入拼音字符,只是選中詞語之后會被截斷,這種體驗也不是我們期待的。

4、完美解決方案

對比前面提到的兩種方法,可以發現,方法3.1在UITextField文本改變之前調用,方法3.2在UITextField文本改變之后調用,這也導致了它們各自存在一定的缺陷。
那么,有沒有一個新的方案,能完美解決這個問題呢?答案是肯定的,也就是同時實現這兩個方法,達到兼而有之。

核心代碼如下

- (void)textFieldDidChange:(UITextField *)textField
{
    if(_maxLength <= 0){
        return;
    }
    
    NSString *text = textField.text;
    UITextRange *selectedRange = [textField markedTextRange];
    UITextPosition *position = [textField positionFromPosition:selectedRange.start offset:0];
    
    //沒有高亮選擇的字,則對已輸入的文字進行字數統計和限制,防止中文/emoj被截斷
    if (!position){
        if (text.length > _maxLength){
            NSRange rangeIndex = [text rangeOfComposedCharacterSequenceAtIndex:_maxLength];
            if (rangeIndex.length == 1){
                textField.text = [text substringToIndex:_maxLength];
            }else{
                if(_maxLength == 1){
                    textField.text = @"";
                }else{
                    NSRange rangeRange = [text rangeOfComposedCharacterSequencesForRange:NSMakeRange(0, _maxLength - 1 )];
                    textField.text = [text substringWithRange:rangeRange];
                }
            }
            
        }
    }
}

#pragma mark -- UITextFieldDelegate
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{  
    if(_maxLength <= 0){
        return YES;
    }
    
    UITextRange *selectedRange = [textField markedTextRange];//高亮選擇的字
    UITextPosition *startPos = [textField positionFromPosition:selectedRange.start offset:0];
    UITextPosition *endPos = [textField positionFromPosition:selectedRange.end offset:0];
    NSInteger markLength = [textField offsetFromPosition:startPos toPosition:endPos];
    
    NSInteger confirmlength =  textField.text.length - markLength - range.length;//已經確認輸入的字符長度
    if(confirmlength >= _maxLength ){
        return NO;
    }
    
    NSInteger allowMaxMarkLength = [self allowMaxMarkLength:_maxLength - confirmlength];
    if(markLength > allowMaxMarkLength ){// && string.length > 0){
        return NO;
    }
    return YES;
}
/**
 主要是用于中文輸入的場景,可根據需要自定義
 剩余的允許輸入的字數較少時,限制拼音字符的輸入,提升體驗
 */
- (NSInteger)allowMaxMarkLength:(NSInteger)remainLength
{
    NSInteger length = 0;
    if(remainLength > 2){
        length = NSIntegerMax;
    }else if(remainLength > 0){
        length = remainLength * 6;  //一個中文對應的拼音一般不超過6個
    }
    
    return length;
}

首先在shouldChangeCharactersInRange方法中計算得到已經確認輸入的文本長度,該長度不包含高亮或者選中的文本。同時計算剩余允許輸入的文本長度,通過allowMaxMarkLength來控制允許輸入的拼音字符的長度。
最后,在textFieldDidChange方法中,對文本進行截斷操作,使得文本長度滿足要求。

完整demo可以參看這里

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容