iOS開發:仿支付寶密碼/常規驗證碼輸入框

.前言

最簡單的方式實現:支付寶的密碼輸入框.以及常規的驗證碼輸入框

先上效果圖

驗證碼輸入框.gif

提供的demo有兩套思路:
1.使用UIKeyInput協議做的文本控件開發
2.使用多個UITextField協同操作

.思路1.

1.首先聲明一個類繼承自UiView.

@interface FQ_TextView : UIView<UIKeyInput,UITextInputTraits> //前者自定義響應鍵盤的文本view.后者定義鍵盤樣式

2.并且聲明幾個常用類型

//驗證碼個數 @property (nonatomic, assign) NSInteger number; //輸入完成的block @property (nonatomic, copy) void(^completeBlock)(); //當前是否顯示黑色小球的樣式 @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];
 }

獲得外部的框:

Pasted Graphic 1.tiff.jpg
4.實現UIKeyInput協議方法
- (BOOL)hasText
{
return self.textTot.length > 0;
}

鍵盤上每輸入一個字符就會調用.主要是獲取鍵盤輸入的字符.在這里做處理
- (void)insertText:(NSString *)text
{

if (self.textTot.length == self.number) { //已經是最長
    return;
}

[self.textTot appendString:text];
[self uploadTextLineViewWithInex:self.textTot.length];
[self setNeedsDisplay];

if (self.textTot.length == self.number) {
    
    if (_completeBlock) {
        _completeBlock();
    }
    [self resignFirstResponder];
    return;
}    
}

鍵盤上刪除按鈕的回調
- (void)deleteBackward
{
if (self.textTot.length == 0) {
return;
}
[self.textTot deleteCharactersInRange:NSMakeRange(self.textTot.length - 1, 1)];
[self uploadTextLineViewWithInex:self.textTot.length];
[self setNeedsDisplay];
}

當然還需要注意:默認不會成為第一響應者.需要重寫canBecomeFirstResponder方法獲取資格

-(BOOL)canBecomeFirstResponder
 {
  return YES;
  }
5.繪制出用戶輸入的文本
 - (void)drawRect:(CGRect)rect {

//設置當前繪制顏色
[[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);
        //這里需要強調一下.文本只能水平居中.所以豎直居中需要自己計算
        [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;
}

到這里一個支付寶密碼的輸入框大致已經完成.是不是超級簡單.只需要處理一點細節即可.我在demo中已經添加了選中樣式.其他的邊框顏色文字顏色等大家自定義即可

.思路2.

來源:看到某個App使用的是有光標的驗證碼輸入框.所以想通過以上方式來添加光標.但是沒有找到相應的資料.所以采用的最直男的方式:

創建多個UITextField,這個看似沒有什么好講的.確實是一個體力活.但里面還是有一些坑

1.首先聲明一個類繼承自UiView
23.同上
4.整體---局部---整體
整體:因為是多個控件組合而成.為了外部方便調用.所以準備了兩個方法.整體的成為或辭去第一響應者
    //整個控件成為第一響應
    -(void)codeView_BecomeFirstResponder
    {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShowOnDelay:) name:UIKeyboardWillShowNotification object:nil];
        [self codeViewBecomeFirstResponderWithTag:self.seletTag];
        self.codeView_IsFirstResponder = YES;
    }

    //整個控件辭去第一響應
    -(void)codeView_ResignFirstResponder
    {
        [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
        //辭去第一響應者
        [self uploadTextLineViewWithInex:1000];
        [self removeXButtonFromKeyBoard];
        [self codeViewResignFirstResponderWithTag:self.seletTag];
        self.codeView_IsFirstResponder = NO;
    }
局部:UITextField之間的切換.我們使用單個控件成為第一響應或辭去第一響應.
   //單個控件成為第一響應者
    - (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;
    }

    //單個控件辭去第一響應者
    -(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];

可以統一使用這個通知做"無文本->有文本"的事情.
為什么說是增加的事情.因為UIKeyboardTypeNumberPad類型的鍵盤.如果文本框沒有文本.我們點擊刪除按鈕.不會收到該通知.

既然這樣?
那么刪除文本框怎么操作呢?

看到一個伙伴的實現方式:每個UITextField選中的時候添加一個@“ “空字符.這樣刪除的時候.就能監聽到通知.這樣也能實現

我采取的方式是粗暴的:直接在鍵盤的刪除按鈕上添加一個刪除按鈕.取代它.

Pasted Graphic 2.tiff.jpg
監聽鍵盤即將出現的通知.

[[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];
        }
    }

有創建.就需要刪除.否則當前界面其他輸入控件喚起鍵盤時.也會觸發通知.就會有問題
- (void)removeXButtonFromKeyBoard
{
[self.deleteBtn removeFromSuperview];
self.deleteBtn.hidden = YES;
self.deleteBtn = nil;
}

所以在整體成為第一響應者的時候注冊通知.辭去第一響應的時候注銷通知

點擊刪除按鈕的響應事件.
  -(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];
        }
    }

如果是本控件的點擊.那么可以很輕松的自己設定為成為或辭去第一響應.
但有一種情況.當我點擊了別的輸入文本時.本控件會辭去第一響應.這個只能使用UITextField的代理.即UITextField結束時調用.
但是局部的UITextField也會有辭去第一響應的時候.
所以此處我使用一個比較low的方法.即找規律.

如果是本控件自己辭去第一響應.那么textFieldShouldEndEditing會調用2次-> 再調textFieldDidEndEditing1次.
如果是點擊其他輸入框辭去的第一響應.那么textFieldShouldEndEditing會調用1次-> 再調textFieldDidEndEditing1次.

 - (BOOL)textFieldShouldEndEditing:(UITextField *)textField
    {
        self.flogIndex ++;
        return YES;
    }

    - (void)textFieldDidEndEditing:(UITextField *)textField
    {
        
        if (self.flogIndex == 2) {
            //那么是正常退出.不用理會
        }else if(self.flogIndex == 1)
        {
            //點擊轉到其他編輯文本的辭去第一響應.應該要刪除刪除按鈕
            NSLog(@"===========self.selectTag %zd",self.seletTag);
            self.codeView_IsFirstResponder = NO;
            [self codeView_ResignFirstResponder];
        }
    }
6.添加選中效果同上.

至此第二種思路也已經完成.能獲取到光標.實現的方式真的很多.如果有時間自己實現和把人家的代碼照搬過來.自己會理解的更透徹

謝謝:iOS 簡易文本控件開發(UIKeyInput協議學習)
謝謝:iOS開發:自定義數字鍵盤(兩種方式)
.希望對你有幫助:https://github.com/FQDEVER/FQ_CodeTextView
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容