序
檢查項目bug的時候偶然發(fā)現(xiàn)察蹲,做過限制(比如說字數(shù)锰瘸、表情)的textField粹淋、textView,觸發(fā)限制條件后监透,會在使用undo功能時crash,之后發(fā)現(xiàn)微信也是一樣的蛋辈。
有朋友問在哪里崩了属拾,不能復現(xiàn),我舉幾個例子,其實有字數(shù)限制的輸入框應該都有問題
隨便試了試qq渐白、yy尊浓、簡書、喜馬拉雅的能輸入漢字的輸入框的字數(shù)限制纯衍,發(fā)現(xiàn)qq一般只提示不限制栋齿;yy禁用了undo;簡書沒做限制托酸;做的最爛的是喜馬拉雅褒颈,做了限制,但是可以輕松突破励堡,輸入任意長度的字符串谷丸。
DEMO
https://github.com/liulishuo/testUndo
思路
出現(xiàn)crash是因為,為了實現(xiàn)輸入的過濾效果应结,會監(jiān)聽輸入框的UIControlEventEditingChanged事件刨疼,截取字符串,手動給輸入框的text屬性賦值鹅龄。正常情況下輸入框執(zhí)行setText:揩慕,默認不會注冊到自己的undoManager上,并且會清空undoManger的undo扮休、redo棧迎卤,這樣并沒有問題,問題是在于監(jiān)聽UIControlEventEditingChanged事件所執(zhí)行的方法里是先對輸入框的text做截取然后執(zhí)行setText:玷坠。
看起來是截取的操作會入undo棧蜗搔,之后的setText:方法并不會清空undo棧,導致做undo操作時八堡,逆操作的是字符串截取的操作樟凄,操作的數(shù)據(jù)對不上,導致崩潰兄渺,這是我覺得比較合理的解釋缝龄。
*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[NSBigMutableString substringWithRange:]: Range {6, 4} out of bounds; string length 6'
以此為前提,我們有三個解決問題的方向:
禁用undo功能挂谍,繞過去叔壤,yy是這樣做的。
使用setText:凳兵,每次在過濾操作時先將setText:注冊到undoManager上百新,再進行setText:賦值操作填帽。我試過不行徒欣。
還是一樣的錯誤-[NSBigMutableString substringWithRange:] range超限了缺亮,setText:的逆操作為啥也是這個澈侠,我不清楚。使用setText:铅辞,并確保和系統(tǒng)默認行為一致厌漂,也就是用setText:賦值,并清空undo棧斟珊。
個人覺得這樣能達到目的苇倡,最方便。
實現(xiàn)
先說微信囤踩,微信的輸入框特點是:
1.漢字聯(lián)想的時的字符數(shù)也一樣有限制
2.文本長度滿了旨椒,輸入框就不能從任意位置插入任何字符(可能是為了規(guī)避系統(tǒng)九宮格鍵盤輸入漢字的問題)
(不太好歸納,我的地址的收貨人輸入框貌似有兩套邏輯堵漱,一個是最大長度16個字综慎,另外一個是最大長度50個字,我每次crash回來都會切換勤庐。示惊。。愉镰,但是兩套邏輯都有各自的問題米罚,有興趣的同學自己試一下,我們這里只討論會crash的情況丈探,也就是最大長度16個字的限制條件下的問題)
所以我一開始以為录择,微信應該是這么實現(xiàn)的
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
//退格
if([string isEqualToString:@""])
{
return YES;
}
//文本長度滿不允許編輯 防止系統(tǒng)九宮格鍵盤在此時傳入數(shù)字標號字符
if(textField.text.length >= kMaxLength)
{
return NO;
}
//非聯(lián)想狀態(tài)
if(!textField.markedTextRange)
{
NSString * tempString = [textField.text stringByReplacingCharactersInRange:range withString:string];
NSLog(@"%@",tempString);
if (tempString.length > kMaxLength)
{
textField.text = [tempString substringToIndex:kMaxLength];
return NO;
}
}
return YES;
}
但是這樣寫不會因為undo而crash,并且還有漢字聯(lián)想無限輸入的bug碗降。
所以微信應該還用了這種方式
//微信
[_tf addTarget:self action:@selector(textFieldTextDidChanged:) forControlEvents:UIControlEventEditingChanged];
- (void)textFieldTextDidChanged:(UITextField *)sender
{
NSString * tempString = sender.text;
if (sender.markedTextRange == nil && tempString.length > kMaxLength)
{
sender.text = [tempString substringToIndex:kMaxLength];
}
}
這種方式除了undo會crash糊肠,沒有其他明顯的漏洞。
修復這個bug遗锣,只需要加一行代碼
- (void)textFieldTextDidChanged:(UITextField *)sender
{
NSString * tempString = sender.text;
if (sender.markedTextRange == nil && tempString.length > kMaxLength)
{
sender.text = [tempString substringToIndex:kMaxLength];
[sender.undoManager removeAllActions];
}
}
末
just for fun 我們來猜一下其他人的實現(xiàn)
yy的實現(xiàn)(機智)
//yy
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
application.applicationSupportsShakeToEdit = NO;
return YES;
}
喜馬拉雅的實現(xiàn)(漏洞最多)
//喜馬拉雅
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if(range.location >= kMaxLength)
{
return NO;
}
else
{
return YES;
}
}
其實我覺得在用戶的輸入階段就屏蔽掉某些可能的輸入,真是一件吃力不討好的事情嗤形。