在開發中我們經常遇到這樣的需求:在UITextField或者UITextView中限制用戶可以輸入的最大字符數。如果是純英文的輸入,很好解決。但是遇到中文輸入法,就會遇到各種坑,而且iOS系統自帶的中文輸入法和第三方輸入法(搜狗,百度)也要區別對待,emoji表情也是個大坑,搞不好就截取錯誤,導致emoji表情顯示錯誤。
下面我們來看看如何填坑。
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;
}
這個方法是UITextField的代理方法,作用如下:
詢問代理在range范圍內的文本是否需要替換為replacementString,Yes就替換,反之就不替換
具體的參考下面這個鏈接:
http://stackoverflow.com/questions/433337/set-the-maximum-character-length-of-a-uitextfield
但是這個方法只能處理純英文的輸入,碰到中文輸入法,就沒法判斷了。
具體原因我們下面分析
2.系統自帶的中文輸入法
使用系統中文輸入法的時候,會出現如下的情況,如圖所示:
我們可以看到在沒有按確認鍵之前,你輸入的任何漢字只是在輸入法的上面顯示出來,在輸入框中被灰色遮蓋的部分只是顯示你輸入的字母,直到你按確認鍵之后,輸入法上面的漢字才會替換輸入框中的被遮蓋的字母。
問題就出在輸入框中被遮蓋的部分(我們暫且稱之為高亮部分,后面都是這樣),因為使用上面的方法計算輸入框中字符數所占據的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表情,然后就出現下面的問題了:
輸入到第九個漢字的時候,我輸入了一個emoji表情,然后就悲劇了,表情顯示不完整
不過,既然問題出現了,我們還是來看看如何解決吧
系統中文輸入法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];
下面是四種情況:
可以看到這個方法的作用就是從rangeOfComposedCharacterSequenceAtIndex:<#(NSUInteger)#>
的參數NSUInteger位置處,向后計算一個完整字符串所占據的range。
這不正是我們想要的效果嗎?
同理rangeOfComposedCharacterSequencesForRange:<#(NSRange)#>
方法就是返回參數range范圍內完整字符串所占據的新的range。
有點拗口,看具體的例子:
可以看到雖然我們的設置的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表情截取錯誤。。。
我趙日天不服啊,繼續解決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];
}
}
}
}
運行下
PS:
上面的方法,還不能對付顏文字,火星文之類的,還是會出現截取錯誤~
更多技術文章歡迎大家訪問我的個人博客:Ximu&Moliang's Blog