本以為實(shí)現(xiàn)一個(gè)輸入驗(yàn)證碼的界面是很容易褐着,但是沒(méi)想到交互起來(lái)還是挺麻煩的逃顶。例如下圖的驗(yàn)證碼輸入校驗(yàn),在許多場(chǎng)景下會(huì)使用到類(lèi)似驗(yàn)證碼顯示與輸入甲捏。這里記錄一下實(shí)現(xiàn)的過(guò)程演熟,踩過(guò)的坑與比較好的方案。這個(gè)過(guò)程重新去了解了drawRect方法司顿,使用了IBInspectable和IB_DESIGNABLE芒粹,封裝控件(代碼與xib都可用)。
嘗試方案
一開(kāi)始實(shí)現(xiàn)時(shí)大溜,我添加4個(gè)textField作為這4個(gè)驗(yàn)證碼的顯示與輸入化漆。接著添加響應(yīng)事件與代理實(shí)現(xiàn)輸入一個(gè)驗(yàn)證碼后,焦點(diǎn)跳轉(zhuǎn)下一個(gè)驗(yàn)證碼钦奋。正向流程沒(méi)有問(wèn)題获三,但在使用退格鍵時(shí),焦點(diǎn)需要回到上一個(gè)锨苏。這里就出現(xiàn)了一個(gè)坑疙教,textField內(nèi)容為空,監(jiān)聽(tīng)不到退格鍵的使用伞租。網(wǎng)上也搜尋相關(guān)方案贞谓,但是都沒(méi)有好的辦法】看到一個(gè)做法是插入在textField為空裸弦,插入一個(gè)字符(一個(gè)沒(méi)有顯示寬度的字符),此時(shí)再按退格鍵就可以監(jiān)聽(tīng)刪除內(nèi)容作喘。
推薦方案
在網(wǎng)上查找相關(guān)實(shí)現(xiàn)的方案理疙,參考不多,筆者看到了一個(gè)鏈接泞坦,如果還有更好推薦留言一下窖贤。
實(shí)現(xiàn)原理是:使用一個(gè)Label來(lái)顯示每一位驗(yàn)證碼。使用一個(gè)隱藏TextField來(lái)接收輸入。將TextField上編輯的內(nèi)容顯示到Label上赃梧。這樣就不會(huì)有太多交互細(xì)節(jié)需要處理延赌,就像這個(gè)驗(yàn)證碼的View是一個(gè)TextField一樣(實(shí)際上也是)沸毁。
封裝與使用
筆者將它進(jìn)行了整理與封裝優(yōu)化:
- 適用于帶邊框顯示的驗(yàn)證碼樣式。
- Label 顯示drawRect時(shí)魔慷,添加邊框抡句,邊框默認(rèn)是顯示矩形區(qū)域外部的膀息,進(jìn)行了處理旧噪,顯示在矩形區(qū)域內(nèi)菌湃。
- 添加間距,邊框?qū)挾鹊葏?shù)巷折,正確顯示邊框與文本压鉴。
- 優(yōu)化支持xib中使用控件。
- 優(yōu)化支持xcode及時(shí)顯示UI盔几。
- 接口簡(jiǎn)單易用晴弃。
文件結(jié)構(gòu):
封裝的Label:
- (void)drawRect:(CGRect)rect {
// 第一次drawRect rect的數(shù)值不正確掩幢,不知道為什么逊拍,麻煩有知道的issue下我。這里處理了一下际邻。
rect.size.width = rect.size.width > self.bounds.size.width ? self.bounds.size.width : rect.size.width;
rect.size.height = rect.size.height > self.bounds.size.height ? self.bounds.size.height : rect.size.height;
//計(jì)算每位驗(yàn)證碼/密碼的所在區(qū)域的寬和高
float width = (rect.size.width - (self.numberOfVertificationCode -1) * self.spacing)/ (float)self.numberOfVertificationCode;;
float height = rect.size.height;
// 畫(huà)矩形邊框 (邊框會(huì)顯示到rect區(qū)域外芯丧,進(jìn)行了處理)
for (int i = 0; i < self.numberOfVertificationCode; i++) {
// 計(jì)算每位驗(yàn)證碼/密碼的繪制區(qū)域
CGRect tempRect = CGRectMake(i * (width +self.spacing) + self.lineWidth, self.lineWidth, width - self.lineWidth*2, height - self.lineWidth*2);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, self.lineWidth);
CGContextSetStrokeColorWithColor(context, self.borderColor.CGColor);
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRoundedRect:tempRect cornerRadius:self.cornerRadius];
CGContextAddPath(context, bezierPath.CGPath);
CGContextStrokePath(context);
}
// 將每位驗(yàn)證碼/密碼繪制到指定區(qū)域
for (int i = 0; i < self.text.length; i++) {
CGRect tempRect = CGRectMake(i * (width +self.spacing) +self.lineWidth, self.lineWidth, width - self.lineWidth * 2, height - self.lineWidth*2);
// 遍歷驗(yàn)證碼/密碼的每個(gè)字符
NSString *charecterString = [NSString stringWithFormat:@"%c", [self.text characterAtIndex:i]];
// 設(shè)置驗(yàn)證碼/密碼的現(xiàn)實(shí)屬性
NSMutableDictionary *attributes = [[NSMutableDictionary alloc] init];
attributes[NSFontAttributeName] = self.font;
attributes[NSForegroundColorAttributeName] = self.textColor;
// 計(jì)算每位驗(yàn)證碼的繪制起點(diǎn)
// 計(jì)算每位驗(yàn)證碼的在指定樣式下的size
CGSize characterSize = [charecterString sizeWithAttributes:attributes];
CGPoint vertificationCodeDrawStartPoint = CGPointMake(CGRectGetMidX(tempRect) - characterSize.width /2, CGRectGetMidY(tempRect) - characterSize.height /2);
// 繪制驗(yàn)證碼
[charecterString drawAtPoint:vertificationCodeDrawStartPoint withAttributes:attributes];
}
}
xib中使用:
直接拖一個(gè)view,修改xib中類(lèi)名即可世曾,可通過(guò)如圖設(shè)置屬性缨恒。
代碼中使用:
- (QTVertificationCodeInputView *)vertificationCodeInputView{
if (!_vertificationCodeInputView) {
_vertificationCodeInputView = [[QTVertificationCodeInputView alloc]initWithFrame:CGRectMake(30, 100, 300, 200)];
_vertificationCodeInputView.numberOfVertificationCode = 4;
_vertificationCodeInputView.lineWidth = 10;
_vertificationCodeInputView.spacing = 10;
_vertificationCodeInputView.cornerRadius = 5;
_vertificationCodeInputView.backgroundColor = [UIColor orangeColor];
}
return _vertificationCodeInputView;
}
疑問(wèn)
UILabel初始化第一次調(diào)用的drawRect,rect的值并不是設(shè)置frame的值轮听,而是稍微大了一點(diǎn)骗露,不知道為什么不對(duì),麻煩有知道的issue下我血巍。這里處理了一下萧锉。
- (void)drawRect:(CGRect)rect {
// 第一次drawRect rect的數(shù)值不正確,不知道為什么述寡,麻煩有知道的issue下我柿隙。這里處理了一下。
rect.size.width = rect.size.width > self.bounds.size.width ? self.bounds.size.width : rect.size.width;
rect.size.height = rect.size.height > self.bounds.size.height ? self.bounds.size.height : rect.size.height;
}
工程文件在github上:https://github.com/casscqt/QTVertificationCodeInputView
參考鏈接:這里