.前言
最簡單的方式實現(xiàn):支付寶的密碼輸入框.以及常規(guī)的驗證碼輸入框
先上效果圖
提供的demo有兩套思路:
1.使用UIKeyInput協(xié)議做的文本控件開發(fā)
2.使用多個UITextField協(xié)同操作
.思路1.
1.首先聲明一個類繼承自UiView.
@interface FQ_TextView : UIView<UIKeyInput,UITextInputTraits> //前者自定義響應(yīng)鍵盤的文本view.后者定義鍵盤樣式
2.并且聲明幾個常用類型
//驗證碼個數(shù) @property (nonatomic, assign) NSInteger number; //輸入完成的block @property (nonatomic, copy) void(^completeBlock)(); //當(dāng)前是否顯示黑色小球的樣式 @property (nonatomic, assign) BOOL mineSecureTextEntry; //是否需要選中效果 @property (nonatomic, assign) BOOL isSelectStatus;
3.UIBezierPath曲線畫外框的線
-(void)addTextLineView
{
UIColor * lineColor = [UIColor grayColor];
UIBezierPath * bezierPath = [UIBezierPath bezierPath];
[bezierPath moveToPoint:CGPointZero];
[bezierPath addLineToPoint:CGPointMake(textViewW, 0)];
[bezierPath addLineToPoint:CGPointMake(textViewW, textViewH)];
[bezierPath addLineToPoint:CGPointMake(0, textViewH)];
[bezierPath addLineToPoint:CGPointMake(0, 0)];
for (int i = 1; i < self.number ; ++i) {
UIBezierPath * bezierPath1 = [UIBezierPath bezierPath];
[bezierPath1 moveToPoint:CGPointMake(i * self.sizeW, 0)];
[bezierPath1 addLineToPoint:CGPointMake(i * self.sizeW, textViewH)];
[bezierPath appendPath:bezierPath1];
}
CAShapeLayer * layer = [[CAShapeLayer alloc]init];
layer.borderColor = lineColor.CGColor;
layer.borderWidth = lineW;
layer.fillColor = [UIColor clearColor].CGColor;
layer.strokeColor = [UIColor grayColor].CGColor;
layer.lineJoin = kCALineJoinRound;
layer.path = bezierPath.CGPath;
layer.frame = self.bounds;
[self.layer addSublayer:layer];
}
獲得外部的框:
4.實現(xiàn)UIKeyInput協(xié)議方法
- (BOOL)hasText
{
return self.textTot.length > 0;
}
鍵盤上每輸入一個字符就會調(diào)用.主要是獲取鍵盤輸入的字符.在這里做處理
- (void)insertText:(NSString *)text
{
if (self.textTot.length == self.number) { //已經(jīng)是最長
return;
}
[self.textTot appendString:text];
[self uploadTextLineViewWithInex:self.textTot.length];
[self setNeedsDisplay];
if (self.textTot.length == self.number) {
if (_completeBlock) {
_completeBlock();
}
[self resignFirstResponder];
return;
}
}
鍵盤上刪除按鈕的回調(diào)
- (void)deleteBackward
{
if (self.textTot.length == 0) {
return;
}
[self.textTot deleteCharactersInRange:NSMakeRange(self.textTot.length - 1, 1)];
[self uploadTextLineViewWithInex:self.textTot.length];
[self setNeedsDisplay];
}
當(dāng)然還需要注意:默認(rèn)不會成為第一響應(yīng)者.需要重寫canBecomeFirstResponder方法獲取資格
-(BOOL)canBecomeFirstResponder
{
return YES;
}
5.繪制出用戶輸入的文本
- (void)drawRect:(CGRect)rect {
//設(shè)置當(dāng)前繪制顏色
[[UIColor blackColor] set];
//加密樣式.
if (self.mineSecureTextEntry) {
for (int i = 0; i < self.textTot.length; ++i) {
UIImage * img = [UIImage imageNamed:@"code_黑點"];
CGSize size = img.size;
CGRect rect = CGRectMake(i * self.sizeW + self.sizeW * 0.5 - img.size.width * 0.5 , textViewH * 0.5 -size.height * 0.5, img.size.width, img.size.height);
[img drawInRect:rect];
}
}else{//非加密樣式
for (int i = 0; i < self.textTot.length; ++i) {
NSString * string = [self.textTot substringWithRange:NSMakeRange(i, 1)];
CGSize size = [string boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:0 attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:18]} context:nil].size;
NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc]init];
style.alignment = NSTextAlignmentCenter;
CGRect rect = CGRectMake(i * self.sizeW, textViewH * 0.5 -size.height * 0.5, self.sizeW, textViewH);
//這里需要強(qiáng)調(diào)一下.文本只能水平居中.所以豎直居中需要自己計算
[string drawInRect:rect withAttributes:@{NSFontAttributeName : [UIFont systemFontOfSize:18],NSParagraphStyleAttributeName:style}];
}
}
}
6.添加選中效果
添加一個CAShapeLayer屬性.
-(void)addTextLineViewSelectLayer
{
CAShapeLayer * layer = [[CAShapeLayer alloc]init];
layer.fillColor = [UIColor clearColor].CGColor;
layer.strokeColor = [UIColor redColor].CGColor;
layer.lineJoin = kCALineJoinRound;
layer.frame = self.bounds;
self.selectLayer = layer;
[self.layer addSublayer:self.selectLayer];
}
更新其路徑值即可.這樣就會覆蓋顯示出來
-(void)uploadTextLineViewWithInex: (NSInteger)index
{
UIBezierPath * bezierPath = [UIBezierPath bezierPath];
if (index == 1000) {
}else{
[bezierPath moveToPoint:CGPointMake(index * self.sizeW, 0)];
[bezierPath addLineToPoint:CGPointMake((index + 1) * self.sizeW, 0)];
[bezierPath addLineToPoint:CGPointMake((index + 1) * self.sizeW, textViewH)];
[bezierPath addLineToPoint:CGPointMake(index * self.sizeW, textViewH)];
[bezierPath addLineToPoint:CGPointMake(index * self.sizeW, 0)];
}
self.selectLayer.path = bezierPath.CGPath;
}
到這里一個支付寶密碼的輸入框大致已經(jīng)完成.是不是超級簡單.只需要處理一點細(xì)節(jié)即可.我在demo中已經(jīng)添加了選中樣式.其他的邊框顏色文字顏色等大家自定義即可
.思路2.
來源:看到某個App使用的是有光標(biāo)的驗證碼輸入框.所以想通過以上方式來添加光標(biāo).但是沒有找到相應(yīng)的資料.所以采用的最直男的方式:
創(chuàng)建多個UITextField,這個看似沒有什么好講的.確實是一個體力活.但里面還是有一些坑
1.首先聲明一個類繼承自UiView
23.同上
4.整體---局部---整體
整體:因為是多個控件組合而成.為了外部方便調(diào)用.所以準(zhǔn)備了兩個方法.整體的成為或辭去第一響應(yīng)者
//整個控件成為第一響應(yīng)
-(void)codeView_BecomeFirstResponder
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShowOnDelay:) name:UIKeyboardWillShowNotification object:nil];
[self codeViewBecomeFirstResponderWithTag:self.seletTag];
self.codeView_IsFirstResponder = YES;
}
//整個控件辭去第一響應(yīng)
-(void)codeView_ResignFirstResponder
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
//辭去第一響應(yīng)者
[self uploadTextLineViewWithInex:1000];
[self removeXButtonFromKeyBoard];
[self codeViewResignFirstResponderWithTag:self.seletTag];
self.codeView_IsFirstResponder = NO;
}
局部:UITextField之間的切換.我們使用單個控件成為第一響應(yīng)或辭去第一響應(yīng).
//單個控件成為第一響應(yīng)者
- (void)codeViewBecomeFirstResponderWithTag:(NSInteger)tag
{
self.seletTag = tag;
[self uploadTextLineViewWithInex:self.seletTag - 1];
UITextField *textField = [self viewWithTag:self.seletTag];
textField.enabled = YES;
[textField becomeFirstResponder];
self.flogIndex = 0;
}
//單個控件辭去第一響應(yīng)者
-(void)codeViewResignFirstResponderWithTag:(NSInteger)tag
{
UITextField *textField = [self viewWithTag:tag];
textField.enabled = NO;
[textField resignFirstResponder];
self.flogIndex = 0;
}
整體:都是UITextField.所以訂閱一個文本更改的通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(changTextFieldTextNotification) name:UITextFieldTextDidChangeNotification object:nil];
可以統(tǒng)一使用這個通知做"無文本->有文本"的事情.
為什么說是增加的事情.因為UIKeyboardTypeNumberPad類型的鍵盤.如果文本框沒有文本.我們點擊刪除按鈕.不會收到該通知.
既然這樣?
那么刪除文本框怎么操作呢?
看到一個伙伴的實現(xiàn)方式:每個UITextField選中的時候添加一個@“ “空字符.這樣刪除的時候.就能監(jiān)聽到通知.這樣也能實現(xiàn)
我采取的方式是粗暴的:直接在鍵盤的刪除按鈕上添加一個刪除按鈕.取代它.
監(jiān)聽鍵盤即將出現(xiàn)的通知.
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShowOnDelay:) name:UIKeyboardWillShowNotification object:nil];
#pragma mark ==========自定義鍵盤.================
-(void)keyboardWillShowOnDelay:(NSNotification *)notification {
[self performSelector:@selector(keyboardWillShow:) withObject:nil afterDelay:0.1];
}
- (void)keyboardWillShow:(NSNotification *)notification {
NSUInteger cnt = [UIApplication sharedApplication].windows.count;
UIWindow *keyboardWindow = [[[UIApplication sharedApplication] windows] objectAtIndex:cnt - 1];
if (!self.deleteBtn.superview) {
[keyboardWindow addSubview:self.deleteBtn];
[keyboardWindow bringSubviewToFront:self.deleteBtn];
}
}
有創(chuàng)建.就需要刪除.否則當(dāng)前界面其他輸入控件喚起鍵盤時.也會觸發(fā)通知.就會有問題
- (void)removeXButtonFromKeyBoard
{
[self.deleteBtn removeFromSuperview];
self.deleteBtn.hidden = YES;
self.deleteBtn = nil;
}
所以在整體成為第一響應(yīng)者的時候注冊通知.辭去第一響應(yīng)的時候注銷通知
點擊刪除按鈕的響應(yīng)事件.
-(void)DeleteButtonDidTouch:(UIButton *)btn
{
NSLog(@"=刪除=====12345555");
if (self.seletTag != 1) {
UITextField *textField = [self viewWithTag:self.seletTag];
[self codeViewResignFirstResponderWithTag:self.seletTag];
textField.text = nil;
self.seletTag -= 1;
UITextField *selectTextField = [self viewWithTag:self.seletTag];
selectTextField.text = nil;
[self codeViewBecomeFirstResponderWithTag:self.seletTag];
}
}
5.本控件與外部的整體交互:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
if (!self.codeView_IsFirstResponder) {
UITextField *textField = [self viewWithTag:self.seletTag];
if (self.codeNum == self.seletTag) {
UITextField *textField = [self viewWithTag:self.seletTag];
textField.text = nil;
}
[self codeView_BecomeFirstResponder];
textField.enabled = YES;
[textField becomeFirstResponder];
}else{
[self codeView_ResignFirstResponder];
}
}
如果是本控件的點擊.那么可以很輕松的自己設(shè)定為成為或辭去第一響應(yīng).
但有一種情況.當(dāng)我點擊了別的輸入文本時.本控件會辭去第一響應(yīng).這個只能使用UITextField的代理.即UITextField結(jié)束時調(diào)用.
但是局部的UITextField也會有辭去第一響應(yīng)的時候.
所以此處我使用一個比較low的方法.即找規(guī)律.
如果是本控件自己辭去第一響應(yīng).那么textFieldShouldEndEditing
會調(diào)用2次-> 再調(diào)textFieldDidEndEditing
1次.
如果是點擊其他輸入框辭去的第一響應(yīng).那么textFieldShouldEndEditing
會調(diào)用1次-> 再調(diào)textFieldDidEndEditing
1次.
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField
{
self.flogIndex ++;
return YES;
}
- (void)textFieldDidEndEditing:(UITextField *)textField
{
if (self.flogIndex == 2) {
//那么是正常退出.不用理會
}else if(self.flogIndex == 1)
{
//點擊轉(zhuǎn)到其他編輯文本的辭去第一響應(yīng).應(yīng)該要刪除刪除按鈕
NSLog(@"===========self.selectTag %zd",self.seletTag);
self.codeView_IsFirstResponder = NO;
[self codeView_ResignFirstResponder];
}
}
6.添加選中效果同上.
至此第二種思路也已經(jīng)完成.能獲取到光標(biāo).實現(xiàn)的方式真的很多.如果有時間自己實現(xiàn)和把人家的代碼照搬過來.自己會理解的更透徹