一開始鬼贱,我以為這是一個屬性設(shè)置問題或者是一個類似helloworld的函數(shù)移怯,但是最后發(fā)現(xiàn),這呀的是一個不小的坑吩愧。
1芋酌、哪些問題需要考慮?
- 輸入字?jǐn)?shù)有哪些雁佳?數(shù)字脐帝、英文同云、中文、emoji堵腹?
- 字?jǐn)?shù)限制怎么定義炸站?一個中文/emoji算一個字?
- 如果限制的是字節(jié)數(shù)呢疚顷?
- 會出現(xiàn)字符截斷問題旱易?
- (中文輸入法)在剩余的輸入字?jǐn)?shù)為1的情況下,是否還允許用戶輸入一堆拼音字符腿堤?
- 要是粘貼進(jìn)一大段文字阀坏,能正確處理?
- ...
2笆檀、測試用例
在介紹用例之前忌堂,我們先來看看微信(iOS V6.5.12.32)是怎么做的。進(jìn)入微信的名字修改界面~
- 純數(shù)字輸入酗洒,允許輸入32個數(shù)字士修。
- 純emoji輸入,允許輸入8個emoji樱衷。
- 純中文輸入棋嘲,允許輸入16個中文
- 先輸入15個中文,然后再輸入一個中文矩桂。是的沸移,此時你只能輸入一個拼音字母(也即前面的“h”問題)。
- 存在上述提到的黏貼問題
基本可以斷定微信的名字是用字節(jié)數(shù)做了限制耍鬓,并且使用的是3.1提到的方法阔籽。
下面是具體的測試用例(最基本的用例就不贅述了):(如無特殊說明,限定輸入字?jǐn)?shù)為10個)
case1:輸入10個中文牲蜀,此時是否還允許有高亮的拼音字母輸入笆制?
case2:輸入10個中文,將鍵盤切換為九宮格英文輸入模式涣达,快速點擊某個字母在辆,最后一個中文是否會被替換?
case3:在剩余的輸入字?jǐn)?shù)為1的情況下度苔,是否還允許用戶輸入一堆高亮拼音字符匆篓?
case4:輸入字?jǐn)?shù)已經(jīng)滿了,此時是否允許粘貼寇窑?鸦概??甩骏?窗市?
case5:限制輸入字符為11個先慷,輸入10個中文,再輸入一個emoji表情咨察,emoji會被截斷论熙?
3、網(wǎng)上參考做法
網(wǎng)上很多博客介紹了相關(guān)的方法摄狱,但是歸根到底就是兩種:
3.1 實現(xiàn) UITextFieldDelegate 協(xié)議中的接口脓诡,這里有說明
- (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;
}
該方法最簡單,在只允許輸入數(shù)字/英文/emoji的情況下媒役,基本能滿足祝谚。但該方法在黏貼文本長度超過允許值時,直接黏貼失斂蕖(也許我們更期待是自動截扔桓)踩验。
在允許輸入中文的情況鸥诽,該方法的缺陷更明顯。假如還剩下一個字可以輸入箕憾,你打算輸入“好”牡借,當(dāng)你輸入字母“h”之后,發(fā)現(xiàn)無法再輸入其他拼音字母了袭异。
3.2 為UITextField實例添加監(jiān)聽 UIControlEventEditingChanged钠龙,然后對其進(jìn)一步處理∮澹可以通過在NSNotificationCenter中添加監(jiān)聽碴里,但是還需手動移除,比較繁瑣上真,所以推薦直接使用下面的方法
[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];
// 沒有高亮選擇的字咬腋,則對已輸入的文字進(jìn)行字?jǐn)?shù)統(tǒng)計和限制
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個中文字符之后就珠,將鍵盤切換為九宮格英文輸入模式寇壳,快速點擊某個字母,看看最后一個中文是否會被替換妻怎?
再者壳炎,當(dāng)輸入達(dá)到限制的字?jǐn)?shù)時,仍舊可以輸入拼音字符逼侦,只是選中詞語之后會被截斷匿辩,這種體驗也不是我們期待的疏日。
4、完美解決方案
對比前面提到的兩種方法撒汉,可以發(fā)現(xiàn)沟优,方法3.1在UITextField文本改變之前調(diào)用,方法3.2在UITextField文本改變之后調(diào)用睬辐,這也導(dǎo)致了它們各自存在一定的缺陷挠阁。
那么,有沒有一個新的方案溯饵,能完美解決這個問題呢?答案是肯定的侵俗,也就是同時實現(xiàn)這兩個方法,達(dá)到兼而有之丰刊。
核心代碼如下
- (void)textFieldDidChange:(UITextField *)textField
{
if(_maxLength <= 0){
return;
}
NSString *text = textField.text;
UITextRange *selectedRange = [textField markedTextRange];
UITextPosition *position = [textField positionFromPosition:selectedRange.start offset:0];
//沒有高亮選擇的字隘谣,則對已輸入的文字進(jìn)行字?jǐn)?shù)統(tǒng)計和限制,防止中文/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;//已經(jīng)確認(rèn)輸入的字符長度
if(confirmlength >= _maxLength ){
return NO;
}
NSInteger allowMaxMarkLength = [self allowMaxMarkLength:_maxLength - confirmlength];
if(markLength > allowMaxMarkLength ){// && string.length > 0){
return NO;
}
return YES;
}
/**
主要是用于中文輸入的場景,可根據(jù)需要自定義
剩余的允許輸入的字?jǐn)?shù)較少時啄巧,限制拼音字符的輸入寻歧,提升體驗
*/
- (NSInteger)allowMaxMarkLength:(NSInteger)remainLength
{
NSInteger length = 0;
if(remainLength > 2){
length = NSIntegerMax;
}else if(remainLength > 0){
length = remainLength * 6; //一個中文對應(yīng)的拼音一般不超過6個
}
return length;
}
首先在shouldChangeCharactersInRange方法中計算得到已經(jīng)確認(rèn)輸入的文本長度,該長度不包含高亮或者選中的文本秩仆。同時計算剩余允許輸入的文本長度码泛,通過allowMaxMarkLength來控制允許輸入的拼音字符的長度。
最后澄耍,在textFieldDidChange方法中噪珊,對文本進(jìn)行截斷操作,使得文本長度滿足要求齐莲。