
更新記錄
增加了對(duì)cocoapods安裝的支持。具體詳情可查看github主頁(yè)。
前言
項(xiàng)目中經(jīng)常會(huì)對(duì)文本輸入的長(zhǎng)度做出限制,這種限制通常有兩種形式:一種是用戶輸入完畢進(jìn)入下一步操作時(shí),程序?qū)ξ谋咀鲂r?yàn),如果校驗(yàn)不通過(guò)給用戶提示;另一種是對(duì)輸入直接做限制,也就是不給用戶輸入限制外的機(jī)會(huì)。
這里我們討論第二種形式,這種方式用戶體驗(yàn)相對(duì)來(lái)說(shuō)更好些。技術(shù)實(shí)現(xiàn)上也也沒(méi)有什么難度,系統(tǒng)提供了文本變化的通知:UITextField對(duì)應(yīng)的通知是UITextFieldTextDidChangeNotification,UITextView對(duì)應(yīng)的是UITextViewTextDidChangeNotification。只要我們注冊(cè)該通知,實(shí)時(shí)對(duì)變化的文本進(jìn)行校驗(yàn)即可。
這里有個(gè)問(wèn)題,就是一個(gè)app中文本框的數(shù)量可能非常多,如果對(duì)每個(gè)控件都寫(xiě)一份代碼,就算copy代碼也是不小的工作量,而且維護(hù)起來(lái)比較困難。于是想做到統(tǒng)一去處理,最后寫(xiě)了一個(gè)簡(jiǎn)單的庫(kù),專門(mén)用來(lái)限制輸入文本的長(zhǎng)度。
幾年過(guò)去了,也不知道現(xiàn)在是不是有更好更強(qiáng)大的庫(kù)來(lái)處理長(zhǎng)度限制。這個(gè)庫(kù)早就放在了github上,也沒(méi)有做宣傳,用的人也不多,現(xiàn)在進(jìn)行紀(jì)念一下,因?yàn)檫@是我做iOS實(shí)現(xiàn)的第一個(gè)庫(kù)。
庫(kù)的地址:TextInputLimit
監(jiān)控所有的UITextField和UITextView
其實(shí)監(jiān)控UITextField和UITextView輸入是系統(tǒng)的工作,只是提供了用戶干預(yù)的手段,就是前面說(shuō)的通知。第一步先注冊(cè)通知,進(jìn)行監(jiān)控:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textFieldViewDidChange:) name:UITextFieldTextDidChangeNotification object: nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textViewDidChange:) name:UITextViewTextDidChangeNotification object: nil];
具體觀察者是誰(shuí)呢?這是實(shí)現(xiàn)全局監(jiān)控的核心,為了監(jiān)控所有的輸入統(tǒng)一處理,就不要和任何具體頁(yè)面以及具體ViewController相關(guān),直接放到一個(gè)單例上,單獨(dú)做這件事。那如何知道當(dāng)前輸入框是不是自己監(jiān)控對(duì)象,以及區(qū)分每個(gè)輸入框的長(zhǎng)度限制?在接收到文本變化通知,具體輸入框會(huì)作為參數(shù)被攜帶過(guò)來(lái),因此只要將限制的長(zhǎng)度綁定到具體輸入框上,然后在接收到通知時(shí)就不用關(guān)心具體是哪個(gè)輸入框。
以UITextField為例:
-(void)textFieldViewDidChange:(NSNotification*)notification {
UITextField *textField = (UITextField *)notification.object;
//獲取輸入框的長(zhǎng)度限制
NSNumber *number = [textField valueForKey:PROPERTY_NAME];
if (number && textField.text.length > [number integerValue] {
//做長(zhǎng)度限制處理
}
}
這里為什么用valueForKey而不直接訪問(wèn)對(duì)象屬性?因?yàn)橐M(jìn)行解耦,即盡量剝離與其他類的關(guān)聯(lián),這樣庫(kù)才能獨(dú)立。對(duì)輸入框加上限制長(zhǎng)度屬性可以采用運(yùn)行時(shí)關(guān)聯(lián)對(duì)象的特性:
-(id)valueForUndefinedKey:(NSString *)key {
if ([key isEqualToString:propertyName]) {
return objc_getAssociatedObject(self, key.UTF8String);
}
return nil;
}
-(void)setValue:(id)value forUndefinedKey:(NSString *)key {
if ([key isEqualToString:propertyName]) {
objc_setAssociatedObject(self, key.UTF8String, value, OBJC_ASSOCIATION_RETAIN);
}
}
所以給具體UITextView設(shè)置限制長(zhǎng)度屬性時(shí),是動(dòng)態(tài)添加上去的。當(dāng)用戶輸入長(zhǎng)度超過(guò)限制時(shí),對(duì)超出的文本自動(dòng)刪除,這樣就實(shí)現(xiàn)還有個(gè)好處:即時(shí)拷貝過(guò)長(zhǎng)文本也會(huì)自動(dòng)截?cái)噙M(jìn)行限制。
-(void)textFieldViewDidChange:(NSNotification*)notification {
UITextField *textField = (UITextField *)notification.object;
NSNumber *number = [textField valueForKey:PROPERTY_NAME];
if (number && textField.text.length > [number integerValue] && textField.markedTextRange == nil) {
textField.text = [textField.text substringWithRange: NSMakeRange(0, [number integerValue])];
}
}
textField.markedTextRange == nil的判斷是為了兼容中文輸入處理,否則輸入中文會(huì)引起崩潰。
最后,為了徹底解耦,防止頭文件依賴,讓觀察者在類加載時(shí)自動(dòng)創(chuàng)建,這樣連初始化類實(shí)例都省了。
到達(dá)限制長(zhǎng)度時(shí)的反饋
實(shí)現(xiàn)長(zhǎng)度自動(dòng)截取,本身對(duì)用戶來(lái)說(shuō)就是一種反饋:你輸入的數(shù)據(jù)只能到這兒了。但有時(shí)可能還需要其他的特殊處理,比如彈個(gè)toast什么的。于是在超過(guò)限制時(shí),再發(fā)個(gè)通知由接收者接收,如果接收者不想額外處理這種反饋,也不需要做任何操作,需要時(shí)才注冊(cè)通知進(jìn)行接收。
這種回饋可能采用協(xié)議更合理些,只是為了實(shí)現(xiàn)完全解耦,防止頭文件依賴,直接采用通知的方式了。完整的處理:
-(void)textFieldViewDidChange:(NSNotification*)notification {
if (!self.enableLimitCount) return;
UITextField *textField = (UITextField *)notification.object;
NSNumber *number = [textField valueForKey:PROPERTY_NAME];
if (number && textField.text.length > [number integerValue] && textField.markedTextRange == nil) {
textField.text = [textField.text substringWithRange: NSMakeRange(0, [number integerValue])];
[[NSNotificationCenter defaultCenter] postNotificationName:@"acceptLimitLength" object: textField];
}
}
如何使用
只需要把LimitInput.h和LimitInput.m引用到工程中,然后在需要進(jìn)行長(zhǎng)度限制的地方設(shè)置限制長(zhǎng)度:
//限制該輸入框長(zhǎng)度為4
[self.textfield setValue:@4 forKey:@"limit"];
這樣就完成了整個(gè)限制流程,很方便不是。注意這里使用setValue: forKey。
如果需要特殊處理,可以注冊(cè)通知acceptLimitLength,當(dāng)輸入超出限制時(shí)會(huì)接收到通知,在通知處理回調(diào)中判斷:是否本輸入框超出限制,如果是就做特殊處理。
//注冊(cè)通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textLimitLenght:) name:@"acceptLimitLength" object:nil];
//接收到長(zhǎng)度超出通知
-(void) textLimitLenght: (NSNotification *) notification {
NSObject *object = notification.object;
if ([object isEqual: self.textview]) {
//收到來(lái)自textview的輸入限制
}
if ([object isEqual: self.textfield]) {
//收到來(lái)自textfield的輸入限制
}
//提示
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"" message:@"您輸入的長(zhǎng)度過(guò)長(zhǎng),自動(dòng)被截?cái)唷? delegate:self cancelButtonTitle:@"確定" otherButtonTitles:nil, nil];
[alert show];
}
后續(xù)
這個(gè)庫(kù)的好處時(shí),在你需要的地方設(shè)置長(zhǎng)度限制,不需要的地方不受任何影響。也就是說(shuō)你把庫(kù)添加到工程中,或從工程中移除都沒(méi)有任何影響,項(xiàng)目都會(huì)正常編譯和運(yùn)行。比如你在項(xiàng)目中使用了該庫(kù),并在某處設(shè)置[self.textfield setValue:@4 forKey:@"limit"];,然后移除該庫(kù),項(xiàng)目照樣正常運(yùn)行(只是長(zhǎng)度限制不再起作用而已)。
另外這個(gè)庫(kù)非常小,也很容易修改實(shí)現(xiàn)訂制需求,比如對(duì)數(shù)字或表情符號(hào)處理。如果有需要,盡管拿去用吧,別客氣!
喜歡的話請(qǐng)給顆星吧:TextInputLimit