詳釋(常見UITextView 輸入之字?jǐn)?shù)限制)之一---固定長度

對(duì)于限制UITextView輸入的字符數(shù)。相信大家在網(wǎng)上見得最多的是實(shí)現(xiàn)UITextViewDelegate

[objc] view plain copy

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range

replacementText:(NSString *)text;//有輸入時(shí)觸但對(duì)于中文鍵盤出示的聯(lián)想字選擇時(shí)不會(huì)觸發(fā)

- (void)textViewDidChange:(UITextView *)textView;//當(dāng)輸入且上面的代碼返回YES時(shí)觸發(fā)。或當(dāng)選擇鍵盤上的聯(lián)想字時(shí)觸發(fā)。

第一個(gè)用于限制輸入,第二個(gè)用于動(dòng)態(tài)計(jì)算剩余字?jǐn)?shù)。聲明

#define MAX_LIMIT_NUMS? ? 100 來限制最大輸入只能100個(gè)字符

詳細(xì)的實(shí)現(xiàn)代碼:

[objc] view plain copy 在CODE上查看代碼片派生到我的代碼片

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range

replacementText:(NSString *)text

{

NSString *comcatstr = [textView.text stringByReplacingCharactersInRange:range withString:text];

NSInteger caninputlen = MAX_LIMIT_NUMS - comcatstr.length;

if (caninputlen >= 0)

{

return YES;

}

else

{

NSInteger len = text.length + caninputlen;

//防止當(dāng)text.length + caninputlen < 0時(shí),使得rg.length為一個(gè)非法最大正數(shù)出錯(cuò)

NSRange rg = {0,MAX(len,0)};

if (rg.length > 0)

{

NSString *s = [text substringWithRange:rg];

[textView setText:[textView.text stringByReplacingCharactersInRange:range withString:s]];

}

return NO;

}

}

- (void)textViewDidChange:(UITextView *)textView

{

NSString? *nsTextContent = textView.text;

NSInteger existTextNum = nsTextContent.length;

if (existTextNum > MAX_LIMIT_NUMS)

{

//截取到最大位置的字符

NSString *s = [nsTextContent substringToIndex:MAX_LIMIT_NUMS];

[textView setText:s];

}

//不讓顯示負(fù)數(shù)

self.lbNums.text = [NSString stringWithFormat:@"%ld/%d",MAX(0,MAX_LIMIT_NUMS - existTextNum),MAX_LIMIT_NUMS];

}

最終運(yùn)行效果:


圖片這么大?^_^。大家看到這是不是就表示OK了呢。就提交代碼了呢?

好,下面就對(duì)上面的簡(jiǎn)單代進(jìn)行逐一BUG找出且進(jìn)行修復(fù)。

1.遺留BUG(只考慮了英文鍵盤下處理)

上述代碼在英文鍵盤下基本上是可以正常,但如果是在中文(生在天朝啊必須得懂中文)或9宮格鍵盤下會(huì)有什么問是呢。下圖是我截圖,當(dāng)輸入到只剩下一個(gè)字時(shí),這時(shí)輸入拼音時(shí),問題出現(xiàn)了,發(fā)現(xiàn)拼音輸不完。另一個(gè)問題是當(dāng)離字?jǐn)?shù)上限差距很大時(shí),輸入拼音會(huì)發(fā)現(xiàn)字?jǐn)?shù)也跟著計(jì)算了。本來還沒有輸入的,此時(shí)開始計(jì)算了,有瘕次。

如圖,在最后一個(gè),本想輸入一個(gè)拼音h開頭的且還沒有出現(xiàn)在推薦字的。哪再輸入第二位拼音時(shí)發(fā)現(xiàn)不能輸了,且字?jǐn)?shù)被計(jì)算了。


對(duì)上述可能有朋友說,這算什么BUG。基本上沒怎么碰到這情況。嗯確實(shí),但如果是在一段字中間插入的時(shí)候呢。這個(gè)是有可能出現(xiàn)的。

哪么怎么樣處理這樣的產(chǎn)生的BUG呢。從分析來看事實(shí)上輸入拼音還處于高亮狀態(tài),哪么有沒有什么辦法來獲取呢。于是呼搜尋中。。。。。。

Ok,確實(shí)有這樣的好。加上后代碼如下:

[objc] view plain copy 在CODE上查看代碼片派生到我的代碼片

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range

replacementText:(NSString *)text

{

UITextRange *selectedRange = [textView markedTextRange];

//獲取高亮部分

UITextPosition *pos = [textView positionFromPosition:selectedRange.start offset:0];

//獲取高亮部分內(nèi)容

//NSString * selectedtext = [textView textInRange:selectedRange];

//如果有高亮且當(dāng)前字?jǐn)?shù)開始位置小于最大限制時(shí)允許輸入

if (selectedRange && pos) {

NSInteger startOffset = [textView offsetFromPosition:textView.beginningOfDocument toPosition:selectedRange.start];

NSInteger endOffset = [textView offsetFromPosition:textView.beginningOfDocument toPosition:selectedRange.end];

NSRange offsetRange = NSMakeRange(startOffset, endOffset - startOffset);

if (offsetRange.location < MAX_LIMIT_NUMS) {

return YES;

}

else

{

return NO;

}

}

NSString *comcatstr = [textView.text stringByReplacingCharactersInRange:range withString:text];

NSInteger caninputlen = MAX_LIMIT_NUMS - comcatstr.length;

if (caninputlen >= 0)

{

return YES;

}

else

{

NSInteger len = text.length + caninputlen;

//防止當(dāng)text.length + caninputlen < 0時(shí),使得rg.length為一個(gè)非法最大正數(shù)出錯(cuò)

NSRange rg = {0,MAX(len,0)};

if (rg.length > 0)

{

NSString *s = [text substringWithRange:rg];

[textView setText:[textView.text stringByReplacingCharactersInRange:range withString:s]];

//既然是超出部分截取了,哪一定是最大限制了。

self.lbNums.text = [NSString stringWithFormat:@"%d/%ld",0,(long)MAX_LIMIT_NUMS];

}

return NO;

}

}

- (void)textViewDidChange:(UITextView *)textView

{

UITextRange *selectedRange = [textView markedTextRange];

//獲取高亮部分

UITextPosition *pos = [textView positionFromPosition:selectedRange.start offset:0];

//如果在變化中是高亮部分在變,就不要計(jì)算字符了

if (selectedRange && pos) {

return;

}

NSString? *nsTextContent = textView.text;

NSInteger existTextNum = nsTextContent.length;

if (existTextNum > MAX_LIMIT_NUMS)

{

//截取到最大位置的字符

NSString *s = [nsTextContent substringToIndex:MAX_LIMIT_NUMS];

[textView setText:s];

}

//不讓顯示負(fù)數(shù) 口口日

self.lbNums.text = [NSString stringWithFormat:@"%ld/%d",MAX(0,MAX_LIMIT_NUMS - existTextNum),MAX_LIMIT_NUMS];

}

效果如下:有拼音輸入還在高亮?xí)r,字?jǐn)?shù)不再計(jì)算。


經(jīng)過上述處理,基本上能支持正常的中,英輸入法鍵盤的字符限制。

到這里可能部分開發(fā)者就可以說大功告成了,可以和BOSS說,BUG弄好了。正準(zhǔn)備看大片時(shí)。。。。。測(cè)試來了,又有BUG了。

凸^-^凸

還有BUG?不可能吧。(很多情況下,是我們一下子思考不完全導(dǎo)至的,也有些場(chǎng)景未能正常預(yù)知所至,因此經(jīng)驗(yàn)至關(guān)重要)

上述的代碼,在輸入中,英文都能正常限制和處理,但如果是輸入的字符帶有emoji表情符,且用戶是使用粘貼的方式就有可能出BUG。看截圖,在最后一位時(shí)貼個(gè)emoji.就變?yōu)閬y碼了?為什么是亂碼?因?yàn)閑moji在IOS中使用的是UTF16也就是占位符是8+8兩個(gè)字節(jié)的(占的長度為2,因此在計(jì)算字?jǐn)?shù)時(shí)一個(gè)表情就占了2即全部表情只能輸入50個(gè),故計(jì)算上也是一個(gè)問題),相當(dāng)于雙UNICODE。所以在使用

substringWithRange或substringToIndex等截取時(shí)就可能正好只取到了某個(gè)emoji的一半字符。如圖顯示:


因此在使用截取字符串函數(shù)時(shí),必須判斷出截取位置是不是emoji字符。(當(dāng)然還有能存在某些不知道的字符在最后一個(gè)時(shí)粘貼截取出問題。)還好ios對(duì)中文的截取還算正確。否則得判斷unicode了。好吧,既然問是出現(xiàn)了,哪必須得解決啊,否則BOSS又要發(fā)威了。。。。

解決思路就是判斷截取的位置是否正好為emoji。一個(gè)比較笨的方式就是判斷截取的位置,先假設(shè)為emoji,取位置前1個(gè)字符和當(dāng)前字串組合(AB)然后用emoji的正則判斷這個(gè)組合后的字符是否為emoji如果是,則說明截取的位置正好是一個(gè)emoji的結(jié)束位。如果組合起來發(fā)現(xiàn)不是emoji,則再來判斷截取位置和+1字符串(注意要判斷是否越界),組合后BC進(jìn)行emoji正則,若為emoji則說明截取的位置正好把emoji劈成兩半了,所以這個(gè)時(shí)候的實(shí)際截取應(yīng)該是當(dāng)前截取位置+1這樣就可以讓emoji截全了。如果不是則放心了,截取的位置不是emoji.(不過不能保證是不是其它雙字節(jié)的)

咱先來看看Emoji輸出的長度和他的實(shí)際字符(在textview中只取一個(gè)笑臉符就可以看出了)

[objc] view plain copy 在CODE上查看代碼片派生到我的代碼片

//encode

NSData *data = [comcatstr dataUsingEncoding:NSNonLossyASCIIStringEncoding];

NSString *goodValue = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

NSLog(@"gv = %@,len = %d",goodValue,goodValue.length);

//decode

data = [goodValue dataUsingEncoding:NSUTF8StringEncoding];

goodValue = [[NSString alloc] initWithData:data encoding:NSNonLossyASCIIStringEncoding];

NSLog(@"gv = %@,len = %d",goodValue,goodValue.length);

輸出日志為:


為解決截取問題,于是呼,我又尋求了一種更為合適的方法(個(gè)人認(rèn)為還不錯(cuò))只要不用于計(jì)算字?jǐn)?shù),還可以效率。

完整代碼:

[objc] view plain copy 在CODE上查看代碼片派生到我的代碼片

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range

replacementText:(NSString *)text

{

UITextRange *selectedRange = [textView markedTextRange];

//獲取高亮部分

UITextPosition *pos = [textView positionFromPosition:selectedRange.start offset:0];

//獲取高亮部分內(nèi)容

//NSString * selectedtext = [textView textInRange:selectedRange];

//如果有高亮且當(dāng)前字?jǐn)?shù)開始位置小于最大限制時(shí)允許輸入

if (selectedRange && pos) {

NSInteger startOffset = [textView offsetFromPosition:textView.beginningOfDocument toPosition:selectedRange.start];

NSInteger endOffset = [textView offsetFromPosition:textView.beginningOfDocument toPosition:selectedRange.end];

NSRange offsetRange = NSMakeRange(startOffset, endOffset - startOffset);

if (offsetRange.location < MAX_LIMIT_NUMS) {

return YES;

}

else

{

return NO;

}

}

NSString *comcatstr = [textView.text stringByReplacingCharactersInRange:range withString:text];

NSInteger caninputlen = MAX_LIMIT_NUMS - comcatstr.length;

if (caninputlen >= 0)

{

return YES;

}

else

{

NSInteger len = text.length + caninputlen;

//防止當(dāng)text.length + caninputlen < 0時(shí),使得rg.length為一個(gè)非法最大正數(shù)出錯(cuò)

NSRange rg = {0,MAX(len,0)};

if (rg.length > 0)

{

NSString *s = @"";

//判斷是否只普通的字符或asc碼(對(duì)于中文和表情返回NO)

BOOL asc = [text canBeConvertedToEncoding:NSASCIIStringEncoding];

if (asc) {

s = [text substringWithRange:rg];//因?yàn)槭莂scii碼直接取就可以了不會(huì)錯(cuò)

}

else

{

__block NSInteger idx = 0;

__block NSString? *trimString = @"";//截取出的字串

//使用字符串遍歷,這個(gè)方法能準(zhǔn)確知道每個(gè)emoji是占一個(gè)unicode還是兩個(gè)

[text enumerateSubstringsInRange:NSMakeRange(0, [text length])

options:NSStringEnumerationByComposedCharacterSequences

usingBlock: ^(NSString* substring, NSRange substringRange, NSRange enclosingRange, BOOL* stop) {

if (idx >= rg.length) {

*stop = YES; //取出所需要就break,提高效率

return ;

}

trimString = [trimString stringByAppendingString:substring];

idx++;

}];

s = trimString;

}

//rang是指從當(dāng)前光標(biāo)處進(jìn)行替換處理(注意如果執(zhí)行此句后面返回的是YES會(huì)觸發(fā)didchange事件)

[textView setText:[textView.text stringByReplacingCharactersInRange:range withString:s]];

//既然是超出部分截取了,哪一定是最大限制了。

self.lbNums.text = [NSString stringWithFormat:@"%d/%ld",0,(long)MAX_LIMIT_NUMS];

}

return NO;

}

}

- (void)textViewDidChange:(UITextView *)textView

{

UITextRange *selectedRange = [textView markedTextRange];

//獲取高亮部分

UITextPosition *pos = [textView positionFromPosition:selectedRange.start offset:0];

//如果在變化中是高亮部分在變,就不要計(jì)算字符了

if (selectedRange && pos) {

return;

}

NSString? *nsTextContent = textView.text;

NSInteger existTextNum = nsTextContent.length;

if (existTextNum > MAX_LIMIT_NUMS)

{

//截取到最大位置的字符(由于超出截部分在should時(shí)被處理了所在這里這了提高效率不再判斷)

NSString *s = [nsTextContent substringToIndex:MAX_LIMIT_NUMS];

[textView setText:s];

}

//不讓顯示負(fù)數(shù) 口口日

self.lbNums.text = [NSString stringWithFormat:@"%ld/%d",MAX(0,MAX_LIMIT_NUMS - existTextNum),MAX_LIMIT_NUMS];

}

回顧一下,文章到此,共解決了哪些易遺留的BUG

1.中,英文字符輸入時(shí)限制。

2.帶emoji時(shí)截取顯示半個(gè)或亂碼字符處理。

好,到此是不是認(rèn)為上面已經(jīng)較為完美了。不然。前面和大家提到過,哪有emoji時(shí)字符數(shù)的計(jì)算就有問題,因?yàn)橐粋€(gè)emoji有可能占長度為2,有個(gè)別是為1的。有朋友會(huì)說,只不過是數(shù)據(jù)顯示不對(duì),但不影響顯示。真的么?哪么你這樣想就錯(cuò)了,隨便取一段帶emoji和普通英文字符,來回復(fù)制你會(huì)發(fā)現(xiàn),這時(shí)你按退格刪除鍵時(shí),不起作用了。為什么呢?原因?yàn)槌鲈诮厝moji代碼部分,因?yàn)闉榉乐馆d半個(gè)emoji,所以將一個(gè)emoji原本的長度len=2當(dāng)1來計(jì)算了,導(dǎo)至最后整體長度大于實(shí)際長度。因此為保證最基本的準(zhǔn)確性,調(diào)整一下截取代碼。

[objc] view plain copy 在CODE上查看代碼片派生到我的代碼片

[text enumerateSubstringsInRange:NSMakeRange(0, [text length])

options:NSStringEnumerationByComposedCharacterSequences

usingBlock: ^(NSString* substring, NSRange substringRange, NSRange enclosingRange, BOOL* stop) {

NSInteger steplen = substring.length;

if (idx >= rg.length) {

*stop = YES; //取出所需要就break,提高效率

return ;

}

trimString = [trimString stringByAppendingString:substring];

idx = idx + steplen;//這里變化了,使用了字串占的長度來作為步長

}];

到此基本上能確定帶emoji時(shí)長度。即長度為100時(shí),純emoji則為50,如果是50個(gè)字符,則最多只能再加25個(gè)emoji.

哪么有朋友又想把一個(gè)emoji當(dāng)作一個(gè)字符來計(jì)算哪怎么處理。我建議大家使用

[objc] view plain copy 在CODE上查看代碼片派生到我的代碼片

[text enumerateSubstringsInRange:NSMakeRange(0, [text length])

options:NSStringEnumerationByComposedCharacterSequences

usingBlock: ^(NSString* substring, NSRange substringRange, NSRange enclosingRange, BOOL* stop) {

這個(gè)方法來處理。網(wǎng)上有好多計(jì)算混合字符串長度的。對(duì)emoji的處理都不正確。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容