在很多App中都有輸入驗證碼的功能需求,最近項目需要也有這個功能。做完之後簡單整理了一下,將實現的基本思路做下記錄。實現後的效果大致如下圖所示,當四位簽到碼全部輸入時,提交按鈕是可以提交的,否則提交按鈕失效,不允許提交。 1 整體佈局 上圖整個界面的佈局很簡單,就不多說了,重點就是中間這一塊的驗證碼 ...
在很多App中都有輸入驗證碼的功能需求,最近項目需要也有這個功能。做完之後簡單整理了一下,將實現的基本思路做下記錄。實現後的效果大致如下圖所示,當四位簽到碼全部輸入時,提交按鈕是可以提交的,否則提交按鈕失效,不允許提交。
1 整體佈局
上圖整個界面的佈局很簡單,就不多說了,重點就是中間這一塊的驗證碼輸入功能,我把它單獨封裝拿出來封裝在一個自定義View(KLCodeResignView)里了,下圖是KLCodeResignView佈局的層次結構。
驗證碼輸入視圖(KLCodeResignView)的最底層用一個透明的UITextField來接收鍵盤的輸入信息,上面則用4個展示視圖(KLCodeView)來分別展示輸入的驗證碼信息,所有的展示視圖(KLCodeView)都放在一個數組中,方便後續的訪問和調用。所以,KLCodeResignView應該向外提供兩個處理入口,驗證碼輸入完成和輸入未完成時的操作入口,併在完成時提供輸入驗證碼信息,這裡我們採用block的方式進行向外提供操作入口。此外,我們還提供了一個可以修改驗證碼位數的入口,調用 initWithCodeBits: 即可設置驗證碼的位數。KLCodeResignView.h以及KLCodeResignView分類的代碼如下:
#import <UIKit/UIKit.h> NS_ASSUME_NONNULL_BEGIN typedef void (^CodeResignCompleted)(NSString *content); typedef void (^CodeResignUnCompleted)(NSString *content); @interface KLCodeResignView : UIView @property (copy, nonatomic) CodeResignCompleted codeResignCompleted; @property (copy, nonatomic) CodeResignUnCompleted codeResignUnCompleted; - (instancetype) initWithCodeBits:(NSInteger)codeBits; @end
@interface KLCodeResignView () <UITextFieldDelegate> @property (strong, nonatomic) UITextField *contentF; //監聽內容輸入 @property (strong, nonatomic) NSArray<KLCodeView *> *codeViewsArr;//展示驗證碼內容的codeView數組 @property (assign, nonatomic) NSInteger currIndex;//當前待輸入的codeView的下標 @property (assign, nonatomic) NSInteger codeBits;//位數 @end
2 註意點
2.1 信息輸入框UITextField
信息輸入框UITextField是最重要的一部分,佈局在KLCodeResignView的最底層,主要作用是用於接收驗證碼的輸入,但是對應的游標肯定是不能顯示出來的,而且該UITextField不能進行複製、粘貼、選擇等操作。所以信息輸入框contentF的配置如下:
- (UITextField *)contentF { if (!_contentF) { _contentF = [[UITextField alloc] init]; //背景顏色和字體顏色都設置為透明的,這樣在界面上就看不到 _contentF.backgroundColor = [UIColor clearColor]; _contentF.textColor = [UIColor clearColor]; _contentF.keyboardType = UIKeyboardTypeNumberPad;//數字鍵盤 _contentF.returnKeyType = UIReturnKeyDone;//完成 _contentF.tintColor = [UIColor clearColor];//設置游標的顏色 _contentF.delegate = self; } return _contentF; }
最後,我們通過添加UITextField的分類來實現屏蔽複製、粘貼、選擇等操作,其實這些都是在UITextField的 - (BOOL)canPerformAction:(SEL)action withSender:(id)sender 進行控制的,返回YES則允許,否則不允許,所以這裡我們不管什麼操作,全部返回NO,這就屏蔽了所有的操作。
@implementation UITextField (ForbiddenSelect) /* 該函數控制是否允許 選擇 全選 剪切 f粘貼等功能,可以針對不同功能進行限制 返回YES表示允許對應的功能,返回NO則表示不允許對應的功能 直接返回NO則表示不允許任何編輯 */ - (BOOL)canPerformAction:(SEL)action withSender:(id)sender { return NO; } @end
2.2 展示視圖(KLCodeView)
展示視圖(KLCodeView)就很簡單了,佈局就是一個UILabel在上面,最下麵一個UIView的下劃線,唯一需要考慮的點就是下劃線的顏色問題,如何根據是否有內容進行顏色變化。這個問題的解決也很簡單,因為這個 UILabel的內容是通過一個屬性text來進行設置的,所以我們重寫text的設置方法就OK了,當設置的text內容不為空時,我們就設置對應的顏色為需要的顏色(藍色),否則設置為灰色。
- (void)setText:(NSString *)text { if (text.length > 0) {//有數據時設置為藍色 self.codeLabel.text = [text substringToIndex:1];//只取一位數 self.lineView.backgroundColor = [UIColor blueColor]; } else { self.codeLabel.text = @""; self.lineView.backgroundColor = [UIColor grayColor]; } }
2.3 輸入邏輯處理
輸入處理邏輯就是在輸入和刪除時進內容進行判斷,並將對應的內容顯示到對應的展示視圖(KLCodeView)中,內容的輸入就都在UITextField的代理UITextFieldDelegate中的 - (BOOL)textField: shouldChangeCharactersInRange: replacementString: 方法中。
- 我們用屬性currIndex來表示當前待輸入的展示視圖KLCodeView的下標,所以,當輸入一個合法的驗證碼時,currIndex要加1,當刪除一個驗證碼時,currIndex要減1,並且當currIndex == 0時,刪除按鈕不起作用,currIndex不再減1了。
- 如果在驗證碼輸入完成和未完成時做不同的處理,通過我們前面提供的兩個block codeResignCompleted 和 codeResignUnCompleted 就可以了,我們再這裡通過判斷currIndex 是否等於 self.codeBits,相等則完成,否則沒有完成,並且調用對應的block進行處理。
- 對輸入內容進行判斷是否是純數字,這個很簡單,判斷方法網上有很多方案,這裡也簡單地貼在下麵的代碼中。
- 對輸入的字元串的長度進行判斷,如果超過當前位數,則輸入無效。
- 完成、刪除操作的判斷一定要在是否是純數字以及位數過長判斷之前,否則可能會導致完成、刪除操作失效。
#pragma mark --- UITextField delegate - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { //完成 則收回鍵盤 if ([string isEqualToString:@"\n"]) { [textField resignFirstResponder]; return NO; } //刪除 操作 if ([string isEqualToString:@""]) { if (self.currIndex == 0) {//待輸入的下標為0時 刪除時下標不變化,否則下標減1 self.codeViewsArr[self.currIndex].text = string; } else { self.codeViewsArr[--self.currIndex].text = string; if (self.codeResignUnCompleted) { NSString *content = [textField.text substringToIndex:self.currIndex]; self.codeResignUnCompleted(content); } } return YES; } //判斷 輸入的是否是純數字,不是純數字 輸入無效 if (![self judgePureInt:string]) { return NO; } //如果輸入的內容超過了驗證碼的長度 則輸入無效 if ((textField.text.length + string.length) > self.codeBits) { return NO; } //輸入的數字,則當前待輸入的下標對應的 view中添加輸入的數字,並且下標加1 self.codeViewsArr[self.currIndex++].text = string; //噹噹前待輸入的下標為codebits時表示已經輸入了對應位數的驗證碼,執行完成操作 if (self.currIndex == self.codeBits && self.codeResignCompleted) { NSString *content = [NSString stringWithFormat:@"%@%@", textField.text, string]; self.codeResignCompleted(content); } else { if (self.codeResignUnCompleted) { NSString *content = [NSString stringWithFormat:@"%@%@", textField.text, string]; self.codeResignUnCompleted(content); } } return YES; }
//判斷一個字元串是都是純數字 - (BOOL)judgePureInt:(NSString *)content { NSScanner *scan = [NSScanner scannerWithString:content]; int val; return [scan scanInt:&val] && [scan isAtEnd]; }
3 使用
使用時只需要創建對應的View進行佈局就OK了,然後設置驗證碼輸入完成和驗證碼輸入未完成對應的處理方案。
- (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor whiteColor]; WEAKSELF KLCodeResignView *codeView = [[KLCodeResignView alloc] initWithCodeBits:4]; codeView.codeResignCompleted = ^(NSString * _Nonnull content) { //對應位數輸入完成時 允許提交按鈕有效 允許提交 NSLog(@"%@", content); weakSelf.submitBtn.enabled = YES; weakSelf.submitBtn.alpha = 1.0f; }; codeView.codeResignUnCompleted = ^(NSString * _Nonnull content) { //對應位數未輸入完成時 提交按鈕失效 不允許提交 weakSelf.submitBtn.enabled = NO; weakSelf.submitBtn.alpha = 0.5f; }; [self.view addSubview:codeView]; [codeView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.mas_equalTo(weakSelf.view).mas_offset(15.0f); make.right.mas_equalTo(weakSelf.view).mas_offset(-15.0f); make.top.mas_equalTo(weakSelf.view).mas_offset(100.0f); make.height.mas_equalTo(40.0f); }]; _submitBtn = [UIButton buttonWithType:UIButtonTypeCustom]; _submitBtn.titleLabel.font = FONT(17.0f); [_submitBtn setTitle:@"提交" forState:UIControlStateNormal]; [_submitBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; [_submitBtn setBackgroundColor:XRGB(3d,9a,e8)]; _submitBtn.enabled = NO; _submitBtn.alpha = 0.5f; _submitBtn.layer.cornerRadius = 5.0f; // [submitBtn addTarget:self action:@selector(submitBtnClicked:) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:_submitBtn]; [_submitBtn mas_makeConstraints:^(MASConstraintMaker *make) { make.left.mas_equalTo(weakSelf.view).mas_offset(20.0f); make.right.mas_equalTo(weakSelf.view).mas_offset(-20.0f); make.top.mas_equalTo(weakSelf.view).mas_offset(260.0f); make.height.mas_equalTo(45.0f); }]; }
所有的代碼如下,主要分為兩個文件,一個 KLCodeResignView.h,一個KLCodeResignView.m,如下:
#import <UIKit/UIKit.h> NS_ASSUME_NONNULL_BEGIN typedef void (^CodeResignCompleted)(NSString *content); typedef void (^CodeResignUnCompleted)(NSString *content); @interface KLCodeResignView : UIView @property (copy, nonatomic) CodeResignCompleted codeResignCompleted; @property (copy, nonatomic) CodeResignUnCompleted codeResignUnCompleted; - (instancetype) initWithCodeBits:(NSInteger)codeBits; @endKLCodeResignView.h
#import "KLCodeResignView.h" #define WEAKSELF typeof(self) __weak weakSelf = self; //自定義 驗證碼展示視圖 view,由一個label和一個下劃線組成 @interface KLCodeView : UIView @property (strong, nonatomic) NSString *text; @property (strong, nonatomic) UILabel *codeLabel; @property (strong, nonatomic) UIView *lineView; @end @interface KLCodeResignView () <UITextFieldDelegate> @property (strong, nonatomic) UITextField *contentF; //監聽內容輸入 @property (strong, nonatomic) NSArray<KLCodeView *> *codeViewsArr;//顯示輸入內容的codeView數組 @property (assign, nonatomic) NSInteger currIndex;//當前待輸入的codeView的下標 @property (assign, nonatomic) NSInteger codeBits;//位數 @end @implementation KLCodeResignView - (instancetype)initWithCodeBits:(NSInteger)codeBits { self = [super init]; self.backgroundColor = [UIColor whiteColor]; self.codeBits = codeBits; if (self) { //驗證碼預設是4位 if (self.codeBits < 1) { self.codeBits = 4; } WEAKSELF [self addSubview:self.contentF]; [self.contentF mas_makeConstraints:^(MASConstraintMaker *make) { make.top.bottom.right.left.mas_equalTo(weakSelf).mas_offset(0.0f); }]; for(NSInteger i = 0; i < self.codeBits; i++) { KLCodeView *codeView = self.codeViewsArr[i]; [self addSubview:codeView]; } } return self; } - (void)layoutSubviews { [super layoutSubviews]; WEAKSELF //設定每個數字之間的間距為數字view寬度的一半 總寬度就是 bits + (bits - 1)* 0.5 CGFloat codeViewWidth = self.bounds.size.width/(self.codeBits * 1.5 - 0.5); for(NSInteger i = 0; i < self.codeBits; i++) { KLCodeView *codeView = self.codeViewsArr[i]; [codeView mas_updateConstraints:^(MASConstraintMaker *make) { CGFloat left = codeViewWidth * 1.5 * i; make.left.mas_equalTo(weakSelf).mas_offset(left); make.top.bottom.mas_equalTo(weakSelf).mas_offset(0.0f); make.width.mas_equalTo(codeViewWidth); }]; } } #pragma mark --- UITextField delegate - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { //完成 則收回鍵盤 if ([string isEqualToString:@"\n"]) { [textField resignFirstResponder]; return NO; } //刪除 操作 if ([string isEqualToString:@""]) { if (self.currIndex == 0) {//待輸入的下標為0時 刪除時下標不變化,否則下標減1 self.codeViewsArr[self.currIndex].text = string; } else { self.codeViewsArr[--self.currIndex].text = string; if (self.codeResignUnCompleted) { NSString *content = [textField.text substringToIndex:self.currIndex]; self.codeResignUnCompleted(content); } } return YES; } //判斷 輸入的是否是純數字,不是純數字 輸入無效 if (![self judgePureInt:string]) { return NO; } //如果輸入的內容超過了驗證碼的長度 則輸入無效 if ((textField.text.length + string.length) > self.codeBits) { return NO; } //輸入的數字,則當前待輸入的下標對應的 view中添加輸入的數字,並且下標加1 self.codeViewsArr[self.currIndex++].text = string; //噹噹前待輸入的下標為codebits時表示已經輸入了對應位數的驗證碼,執行完成操作 if (self.currIndex == self.codeBits && self.codeResignCompleted) { NSString *content = [NSString stringWithFormat:@"%@%@", textField.text, string]; self.codeResignCompleted(content); } else { if (self.codeResignUnCompleted) { NSString *content = [NSString stringWithFormat:@"%@%@", textField.text, string]; self.codeResignUnCompleted(content); } } return YES; } - (UITextField *)contentF { if (!_contentF) { _contentF = [[UITextField alloc] init]; //背景顏色和字體顏色都設置為透明的,這樣在界面上就看不到 _contentF.backgroundColor = [UIColor clearColor]; _contentF.textColor = [UIColor clearColor]; _contentF.keyboardType = UIKeyboardTypeNumberPad;//數字鍵盤 _contentF.returnKeyType = UIReturnKeyDone;//完成 _contentF.tintColor = [UIColor clearColor];//設置游標的顏色 _contentF.delegate = self; } return _contentF; } - (NSArray<KLCodeView *> *)codeViewsArr { if (!_codeViewsArr) { NSMutableArray *arr = [NSMutableArray array]; for (NSInteger i = 0; i < self.codeBits; i++) { KLCodeView *codeView = [[KLCodeView alloc] init]; [arr addObject:codeView]; } _codeViewsArr = [NSArray arrayWithArray:arr]; } return _codeViewsArr; } //判斷一個字元串是都是純數字 - (BOOL)judgePureInt:(NSString *)content { NSScanner *scan = [NSScanner scannerWithString:content]; int val; return [scan scanInt:&val] && [scan isAtEnd]; } @end @implementation UITextField (ForbiddenSelect) /* 該函數控制是否允許 選擇 全選 剪切 f粘貼等功能,可以針對不同功能進行限制 返回YES表示允許對應的功能,返回NO則表示不允許對應的功能 直接返回NO則表示不允許任何編輯 */ - (BOOL)canPerformAction:(SEL)action withSender:(id)sender { return NO; } @end //驗證碼展示視圖 的實現 @implementation KLCodeView - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { WEAKSELF self.backgroundColor = [UIColor whiteColor]; self.userInteractionEnabled = NO; //數字編碼 label _codeLabel = [[UILabel alloc] init]; _codeLabel.textColor = [UIColor blueColor]; _codeLabel.font = FONT(25.0f); _codeLabel.textAlignment = NSTextAlignmentCenter; [self addSubview:_codeLabel]; [_codeLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.top.left.right.mas_equalTo(weakSelf).mas_offset(0.0f); make.bottom.mas_equalTo(weakSelf).mas_offset(-10.0f); }]; _lineView = [[UIView alloc] init]; _lineView.backgroundColor = [UIColor grayColor]; [self addSubview:_lineView]; [_lineView mas_makeConstraints:^(MASConstraintMaker *make) { make.bottom.left.right.mas_equalTo(weakSelf).mas_offset(0.0f); make.height.mas_equalTo(2.0f); }]; } return self; } - (void)setText:(NSString *)text { if (text.length > 0) { self.codeLabel.text = [text substringToIndex:1]; self.lineView.backgroundColor = [UIColor blueColor]; } else { self.codeLabel.text = @""; self.lineView.backgroundColor = [UIColor grayColor]; } } @endKLCodeResignView.m