获取验证码的按钮状态变化的实现

我们现在的很多应用中都需要用户注册之后登陆,而其方式,在移动端多是通过手机号注册和登陆,那么获取验证码的操作就必不可少,包括一些有提现功能的应用,需要在提现时验证用户手机号,都是其应用的场景;

今天刚好有个朋友问到了这里,我就看了一下之前写的代码,那段代码又乱又杂,实现方式上也大可优化一下,之前只是为自己项目定制的,正好借这个机会重新梳理一番;

该功能的实现前前后后我改了3遍,走的弯路这里就不写了,直接来分析当前的实现方案:

首先,我们列一下需求:

1)点击“获取验证码”按钮时,实现验证码倒计时120秒,期间按钮不可点击,走完120秒时候,按钮重新回到“获取验证码”按钮可点击的状态;

2)程序运行的情况下,只要120秒没走完,那么每次进入到该界面时,都需要继续进行倒计时;

3)若用户将程序进程杀掉,重新启动应用时,进入该界面,仍需继续进行倒计时;

基本的要求就是这样的,其中比较重要的有这样几点:

1)按钮存在两种状态的切换,一种是倒计时状态(每秒都会变化)不可点击;一种是“获取验证码状态” 可点击;

2)无论什么情况下,只要验证码没走完,进入该界面,就必须接着让他走完,这里涉及到一开始显示的倒计时数的判断和计算;

3)在倒计时的过程中,我们需要每秒回调刷新界面显示;

基于此,代码的实现方案如下:(为了方便 我就直接粘代码了)

新建LLValidCodeManager类,用于实现该功能;

.h文件内容如下:

#define VALIDCODE_MAX_TIMECOUNT 120

typedef enum {
    
    LLGETVALIDCODE_TYPE_REGISTER = 0,           //注册
    LLGETVALIDCODE_TYPE_FORGETPASSWORD,         //忘记密码
    LLGETVALIDCODE_TYPE_WITHDRAW,               //提现
    
    LLGETVALIDCODE_TYPE_
    
}LLGETVALIDCODE_TYPE;

@interface LLValidCodeManager : NSObject{
    NSTimer    * registTimer;//定时器
}

typedef void (^EverySecondBlock)( BOOL isUsing , long long currentTimeCounts);
@property (nonatomic,copy)EverySecondBlock block;

@property (nonatomic , assign) long long    currentTimeCounts;  //已记录的时间增量(单位:秒)
@property (nonatomic , assign) BOOL         isUsing;            //是否在使用中标识

@property (nonatomic , strong) NSString * validCodeTypeStr;     //获取验证码类型的字符串标示(用于本地存储)

/**
 * 初始化方法(必须调用)
 */
-(instancetype)init;

/**
 * 定时器启动 则每秒回调
 */
-(void)beginTimerWithValidCodeType:(LLGETVALIDCODE_TYPE)type EverySecondBlock:(EverySecondBlock)block;

/**
 * 同步定时状态操作 若定时器需要启动 则启动定时器 每秒回调
 */
-(void)assignTimerUsingStateWithValidCodeType:(LLGETVALIDCODE_TYPE)type EverySecondBlock:(EverySecondBlock)block;
.m文件内容如下:

-(instancetype)init{
  
    self = [super init];
    if (self) {
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(APPWillBecomeActive)
                                                     name:UIApplicationWillEnterForegroundNotification object:nil];
    }
    
    return self;
    
}
-(void)dealloc{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil];
}

-(void)beginTimerWithValidCodeType:(LLGETVALIDCODE_TYPE)type EverySecondBlock:(EverySecondBlock)block{    self.block = block;
    [self assignValidCodeTypeStr:type];
    [self beginTimer];
    
}
-(void)assignTimerUsingStateWithValidCodeType:(LLGETVALIDCODE_TYPE)type EverySecondBlock:(EverySecondBlock)block{
    self.block = block;
    [self assignValidCodeTypeStr:type];
    [self assignTimerUsingState];
    
}
-(void)assignValidCodeTypeStr:(LLGETVALIDCODE_TYPE)type{
    
    if (type == LLGETVALIDCODE_TYPE_REGISTER) {
        self.validCodeTypeStr = @"LLGETVALIDCODE_TYPE_REGISTER";

    }else if (type == LLGETVALIDCODE_TYPE_FORGETPASSWORD){
        self.validCodeTypeStr = @"LLGETVALIDCODE_TYPE_FORGETPASSWORD";
        
    }else if (type == LLGETVALIDCODE_TYPE_WITHDRAW){
        self.validCodeTypeStr = @"LLGETVALIDCODE_TYPE_WITHDRAW";
        
    }
}
#pragma mark -启动定时器 和 同步定时器状态
-(void)beginTimer
{
    
    long long  currentDate = [[NSDate getCurrentTime] longLongValue]/1000;
    
    //保存开始时间
    [[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithLongLong:currentDate] forKey:self.validCodeTypeStr];
    [[NSUserDefaults standardUserDefaults] synchronize];
    
    [self ll_InitTimer];
    
}
-(void)assignTimerUsingState{
    
    if (self.isUsing) {
        return;
        
    }else{//程序重新启动 或者 没在使用
        
        long long currentDate   = [[NSDate getCurrentTime] longLongValue]/1000;
        long long beginDate = [[[NSUserDefaults standardUserDefaults] valueForKey:self.validCodeTypeStr] longLongValue];
        
        if ((currentDate - beginDate) >= VALIDCODE_MAX_TIMECOUNT) {//倒计时已结束
            return;
            
        }else if ((currentDate - beginDate) >0){//倒计时还未结束
            
            _currentTimeCounts = currentDate - beginDate;
            
            [self ll_InitTimer];
            return;
        }else{
            //等于0的情况   小于0时可能手机时间已被修改
            return ;
        }
    }
}
//定时调用方法的执行内容
- (void)timerAction
{
    _currentTimeCounts += 1;
    
    //倒计时完毕
    if (_currentTimeCounts >= VALIDCODE_MAX_TIMECOUNT) {//倒计时时间的宏 当前设置为120s
        _currentTimeCounts = 0;
        [self ll_CancelTimer];
    }
    
    self.block(self.isUsing , _currentTimeCounts);
    
}
#pragma mark -初始化定时器和取消定时器
-(void)ll_InitTimer{
    [self ll_CancelTimer];
    self.isUsing = YES;
    registTimer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
    // 将timer加入到当前的run loop中,并将run loop mode设置为UITrackingRunLoopMode或NSRunLoopCommonModes,这样,即使用户触摸了屏幕,也不会导致timer暂停界面刷新.
    [[NSRunLoop currentRunLoop] addTimer:registTimer forMode:UITrackingRunLoopMode];
    [registTimer fire];
}
-(void)ll_CancelTimer{
    self.isUsing = NO;
    if (registTimer) {//废除定时器
        if ([registTimer isValid]) {
            [registTimer invalidate];
        }
        registTimer = nil;
    }
}

#pragma mark -监听进入后台 以及 从后台进入前台的通知
//一进入前台 若当前定时器在使用 则判断是否需要重新启动定时器
-(void)APPWillBecomeActive
{
    if (self.isUsing) {
        
        long long  currentDate = [[NSDate getCurrentTime] longLongValue]/1000;
        long long  beginDate = [[[NSUserDefaults standardUserDefaults] valueForKey:self.validCodeTypeStr] longLongValue];
        
        _currentTimeCounts = currentDate - beginDate;
        [registTimer fire];
    }
}
其中关于当前时间的获取,可用如下方法:

+(NSString *)getCurrentTime
{
    //获取当前时间的秒数
    NSTimeInterval time = [[NSDate date] timeIntervalSince1970];
    long long date = (long long)(time *1000);//精确到毫秒
    NSString * timeStr = [NSString stringWithFormat:@"%lld",date];
    return timeStr;
}
在使用中需要在控件视图添加时,调用.h中第二个有block的方法,进行当前获取验证码类型对应的按钮状态同步;

在点击“获取验证码”按钮时,调用.h中第一个有block的方法,进行定时器的启动;

相应的block中我们可以进行按钮的两种状态的切换,相应的代码示例如下:

if (isUsing) {
            [self.getValidCode setTitle:[NSString stringWithFormat:@"%llds",VALIDCODE_MAX_TIMECOUNT - currentTimeCounts] forState:UIControlStateNormal];
            self.getValidCode.userInteractionEnabled = NO;
            [self.getValidCode setTitleColor:COLOR_RGB_HEX(0x999999) forState:UIControlStateNormal];
        }else{
            [self.getValidCode setTitle:@"获取验证码" forState:UIControlStateNormal];
            self.getValidCode.userInteractionEnabled = YES;
            [self.getValidCode setTitleColor:COLOR_RGB_HEX(0x666666) forState:UIControlStateNormal];
        }

代码加完了,接下来解释一下,这部分代码是如何满足上述需求的:

在实现时,我们需要注意一点,不要将按钮的显示控制混合到这个功能实现类中,那么刷新按钮显示,回调就一定要有,我们设置的block就是这个作用,传递的两个参数意义也比较明显,一个标示定时器是不是再走,对应按钮状态的切换,另一个标示定时器已经记录的时间增量,用于界面的显示;

定时器每秒都会执行相应的方法,对应的每秒都要进行回调,这就说明了第一点和第三点;

再说第二点:

我们将每次点击“获取验证码”按钮时的时间记录在本地,在以下几种情况根据当前的时间与本地的时间差 与 倒计时最大值的比较,判断出是否需要新的定时器(如果已有定时器直接启动就行,没有的话,实例化对象,初始化新的定时器进行倒计时):

1)从后台切回前台的情况;

2)退出当前界面 或 进行界面切换的情况;

3)杀掉程序重新启动的情况;

终于说完了,希望我说的还明白。。。

这个功能,相信不少的同仁都有实现过,我的只是实现的一种,我也相信会有更好的方案;如果大家有用到,在调试过程遇到了问题,可以给我留言。

这段代码是我和我的同事一起实现得出来的方案,感谢我的同事,是这篇blog成为可能。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值