iOS學習——輸入驗證碼界面封裝


  在很多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;

@end
KLCodeResignView.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];
    }
}


@end
KLCodeResignView.m

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM