iOS验证码倒计时实现,退出进入以后继续倒计时

需求

App中有很多页面地方要发送验证码,涉及到验证码的地方肯定会有倒计时功能。产品要求发送验证码以后,在倒计时结束之前不重复发送验证码。

第一步

首先实现倒计时功能,以登录界面为例,用户输入手机号以后,需要点击按钮发送验证码,发送验证码成功以后,会调用下面方法,实现按钮倒计时功能

- (void)timerCountDownWithType:(BOUCountDownType)countDownType {
    
    _countDonwnType = countDownType;
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), 1.0 * NSEC_PER_SEC, 0);
    
    NSTimeInterval seconds = kMaxCountDownTime;
    NSDate *endTime = [NSDate dateWithTimeIntervalSinceNow:seconds];
    dispatch_source_set_event_handler(_timer, ^{
    
        int interval = [endTime timeIntervalSinceNow];
        if (interval <= 0) {
            dispatch_source_cancel(_timer);
            dispatch_async(dispatch_get_main_queue(), ^{
            	//倒计时结束,改变按钮状态,做相关操作
            	[self.meeageButton setTitle:@"重新获取" forState:UIControlStateNormal];
                [self.meeageButton setEnabled:YES];
            });
        }
        else {
            dispatch_async(dispatch_get_main_queue(), ^{
                //倒计时中 interval是倒计时描述,可以用此值更新按钮文字
                [self.meeageButton setTitle:@(interval).stringValue forState:UIControlStateNormal];
                [self.meeageButton setEnabled:NO];
            });
        }
    });
    dispatch_resume(_timer);
}

上面方法实现了按钮的倒计时功能,但是有个问题,如果不点击返回按钮,离开当前页面的话,那么倒计时正常,但是当返回上层界面,再次进入本页面后,倒计时按钮会重置。用户可以重新发送验证码,但是上次的倒计时时间还未到,这与产品需求不符合,所以上面的方案需要调整。

第二步

经过思考以后,决定将倒计时功能单独封装到一个类中,避免频繁书写重复代码。

1.新建BOUTimerManager类,继承自NSObject

由于有多个页面需要实现倒计时功能,为了区分倒计时所属页面,定义以下枚举类型:

typedef NS_ENUM(NSInteger, BOUCountDownType) {
    BOUCountDownTypeLogin,//登录界面
    BOUCountDownTypeFindPassword,//忘记密码界面
    BOUCountDownTypeRegister,//注册界面
    BOUCountDownTypeModifyPhone,//修改手机号界面
};

BOUTimerManager.h文件中定义以下方法:

+ (instancetype)shareInstance;//此方法实现单例

- (void)timerCountDownWithType:(BOUCountDownType)countDownType;//调用此方法开始倒计时,根据传入的type值判断开始哪个页面的倒计时。

- (void)cancelTimerWithType:(BOUCountDownType)countDownType;//调用此方法取消倒计时,根据传入的type值判断取消的是哪个页面的倒计时。

在倒计时过程中,响应界面需要根据是倒计时中或者倒计时完成处理相关页面逻辑,我在这里使用发送通知的方法,在倒计时过程中和倒计时完成时发送通知,页面注册通知以后可以接收到倒计时状态,所以在BOUTimerManager.h还需定义以下内容:

#define kLoginCountDownCompletedNotification            @"kLoginCountDownCompletedNotification"
#define kFindPasswordCountDownCompletedNotification     @"kFindPasswordCountDownCompletedNotification"
#define kRegisterCountDownCompletedNotification            @"kRegisterCountDownCompletedNotification"
#define kModifyPhoneCountDownCompletedNotification            @"kModifyPhoneCountDownCompletedNotification"

#define kLoginCountDownExecutingNotification            @"kLoginCountDownExecutingNotification"
#define kFindPasswordCountDownExecutingNotification     @"kFindPasswordCountDownExecutingNotification"
#define kRegisterCountDownExecutingNotification            @"kRegisterCountDownExecutingNotification"
#define kModifyPhoneCountDownExecutingNotification            @"kModifyPhoneCountDownExecutingNotification"
BOUTimerManager.h全部内容如下:
#import <Foundation/Foundation.h>

#define kLoginCountDownCompletedNotification            @"kLoginCountDownCompletedNotification"
#define kFindPasswordCountDownCompletedNotification     @"kFindPasswordCountDownCompletedNotification"
#define kRegisterCountDownCompletedNotification            @"kRegisterCountDownCompletedNotification"
#define kModifyPhoneCountDownCompletedNotification            @"kModifyPhoneCountDownCompletedNotification"

#define kLoginCountDownExecutingNotification            @"kLoginCountDownExecutingNotification"
#define kFindPasswordCountDownExecutingNotification     @"kFindPasswordCountDownExecutingNotification"
#define kRegisterCountDownExecutingNotification            @"kRegisterCountDownExecutingNotification"
#define kModifyPhoneCountDownExecutingNotification            @"kModifyPhoneCountDownExecutingNotification"

NS_ASSUME_NONNULL_BEGIN

typedef NS_ENUM(NSInteger, BOUCountDownType) {
    BOUCountDownTypeLogin,
    BOUCountDownTypeFindPassword,
    BOUCountDownTypeRegister,
    BOUCountDownTypeModifyPhone,
};


@interface BOUTimerManager : NSObject

DEF_SINGLETON(BOUTimerManager);

- (void)timerCountDownWithType:(BOUCountDownType)countDownType;

- (void)cancelTimerWithType:(BOUCountDownType)countDownType;

@end

NS_ASSUME_NONNULL_END

BOUTimerManager.m实现全部内容如下:
#import "BOUTimerManager.h"

#define kMaxCountDownTime           60//倒计时时间,可自定义

@interface BOUTimerManager ()

@property (nonatomic, assign) BOUCountDownType countDonwnType;

@property (nonatomic, nullable, strong) dispatch_source_t loginTimer;//登录界面倒计时timer

@property (nonatomic, nullable, strong) dispatch_source_t findPwdTimer;//找回密码界面倒计时timer

@property (nonatomic, nullable, strong) dispatch_source_t registerTimer;//注册界面倒计时timer

@property (nonatomic, nullable, strong) dispatch_source_t modifyPhoneTimer;//修改手机号界面倒计时timer

@end

@implementation BOUTimerManager

IMP_SINGLETON(BOUTimerManager);

- (void)timerCountDownWithType:(BOUCountDownType)countDownType {
    
    _countDonwnType = countDownType;
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), 1.0 * NSEC_PER_SEC, 0);
    
    NSTimeInterval seconds = kMaxCountDownTime;
    NSDate *endTime = [NSDate dateWithTimeIntervalSinceNow:seconds];
    dispatch_source_set_event_handler(_timer, ^{
    
        int interval = [endTime timeIntervalSinceNow];
        if (interval <= 0) {
            dispatch_source_cancel(_timer);
            dispatch_async(dispatch_get_main_queue(), ^{
                
                if ([_timer isEqual:self.loginTimer]) {
                    [[NSNotificationCenter defaultCenter] postNotificationName:kLoginCountDownCompletedNotification object:@(interval)];
                } else if ([_timer isEqual:self.findPwdTimer]) {
                    [[NSNotificationCenter defaultCenter] postNotificationName:kFindPasswordCountDownCompletedNotification object:@(interval)];
                } else if ([_timer isEqual:self.registerTimer]) {
                    [[NSNotificationCenter defaultCenter] postNotificationName:kRegisterCountDownCompletedNotification object:@(interval)];
                } else if ([_timer isEqual:self.modifyPhoneTimer]) {
                    [[NSNotificationCenter defaultCenter] postNotificationName:kModifyPhoneCountDownCompletedNotification object:@(interval)];
                }
            
            });
        }
        else {
            dispatch_async(dispatch_get_main_queue(), ^{
                
                if ([_timer isEqual:self.loginTimer]) {
                    [[NSNotificationCenter defaultCenter] postNotificationName:kLoginCountDownExecutingNotification object:@(interval)];
                } else if ([_timer isEqual:self.findPwdTimer]) {
                    [[NSNotificationCenter defaultCenter] postNotificationName:kFindPasswordCountDownExecutingNotification object:@(interval)];
                } else if ([_timer isEqual:self.registerTimer]) {
                    [[NSNotificationCenter defaultCenter] postNotificationName:kRegisterCountDownExecutingNotification object:@(interval)];
                } else if ([_timer isEqual:self.modifyPhoneTimer]) {
                    [[NSNotificationCenter defaultCenter] postNotificationName:kModifyPhoneCountDownExecutingNotification object:@(interval)];
                }
                
            });
        }
    });
    
    if (self.countDonwnType == BOUCountDownTypeLogin) {
        self.loginTimer = _timer;
    } else if (self.countDonwnType == BOUCountDownTypeFindPassword) {
        self.findPwdTimer = _timer;
    } else if (self.countDonwnType == BOUCountDownTypeRegister) {
        self.registerTimer = _timer;
    } else if (self.countDonwnType == BOUCountDownTypeModifyPhone) {
        self.modifyPhoneTimer = _timer;
    }
    
    dispatch_resume(_timer);
}

- (void)cancelTimerWithType:(BOUCountDownType)countDownType {
    switch (countDownType) {
        case BOUCountDownTypeLogin:
            if (self.loginTimer) {
                dispatch_source_cancel(self.loginTimer);
                self.loginTimer = nil;
            }
            
            break;
        case BOUCountDownTypeRegister:
            if (self.registerTimer) {
                dispatch_source_cancel(self.registerTimer);
                self.registerTimer = nil;
            }
            
            break;
        case BOUCountDownTypeModifyPhone:
            if (self.registerTimer) {
                dispatch_source_cancel(self.modifyPhoneTimer);
                self.registerTimer = nil;
            }
            
            break;
        case BOUCountDownTypeFindPassword:
            if (self.registerTimer) {
                dispatch_source_cancel(self.findPwdTimer);
                self.registerTimer = nil;
            }
            
            break;
        default:
            break;
    }
}

@end

DEF_SINGLETON是单例声明的宏定义,IMP_SINGLETON是单例实现的宏定义

#undef    DEF_SINGLETON
#define DEF_SINGLETON( __class ) \
+ (__class *)sharedInstance;

#undef    IMP_SINGLETON
#define IMP_SINGLETON( __class ) \
+ (__class *)sharedInstance \
{ \
static dispatch_once_t once; \
static __class * __singleton__; \
dispatch_once( &once, ^{ __singleton__ = [[__class alloc] init]; } ); \
return __singleton__; \
}

2.控制器中处理逻辑,以登录界面为例

- (instancetype)init- (instancetype)initWithCoder:(NSCoder *)coder方法中注册倒计时通知事件

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(loginTimerCountDownCompleted) name:kLoginCountDownCompletedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(loginTimerCountDownExecutingWithTimeOut:) name:kLoginCountDownExecutingNotification object:nil];


#pragma mark - NSNotification 处理倒计时事件

- (void)loginTimerCountDownExecutingWithTimeOut:(NSNotification *)notification {
    NSInteger timeOut = [notification.object integerValue];
    NSString *timeStr = [NSString stringWithFormat:@"(%.2ld)重新获取",(long)timeOut];
    self.btnCountDown.selected = YES;//此处的 self.topView.btnCountDown换成自己的button
    [self.btnCountDown setTitle:timeStr forState:UIControlStateNormal];//此处的 self.topView.btnCountDown换成自己的button
    [self.btnCountDown setTitleColor:ZYC_COLOR_WITH_HEX(0x999999) forState:UIControlStateNormal];
    self.btnCountDown.userInteractionEnabled = NO;
}

- (void)loginTimerCountDownCompleted {
    self.btnCountDown.selected = NO;//此处的 self.topView.btnCountDown换成自己的button
    [self.btnCountDown setTitle:@"获取验证码" forState:UIControlStateNormal];//此处的 self.topView.btnCountDown换成自己的button
    [self.btnCountDown setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];//此处的 self.topView.btnCountDown换成自己的button
    self.btnCountDown.userInteractionEnabled = YES;//此处的 self.topView.btnCountDown换成自己的button
}

dealloc方法中销毁注册通知

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

输入手机号码以后,点击发送验证码,后台接口返回成功以后,开始倒计时

- (void)sendSMSRequestWithPhone:(NSString *)phoneNum sender:(UIButton *)sender {
	//模拟网络请求
	//...
	//开始倒计时
	[[BOUTimerManager sharedInstance] timerCountDownWithType:BOUCountDownTypeLogin];
}

点击登录按钮,后台接口返回以后,取消登录界面验证码倒计时

- (void)clickLoginAction:(UIButton *)sender {
	//模拟网络请求
	//...
	//取消登录界面倒计时
	[[BOUTimerManager sharedInstance] cancelTimerWithType:BOUCountDownTypeLogin];
}

结尾

demo地址 https://github.com/latacat/iOS_Demos/tree/main/AuthCodeTest

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值