美文网首页
iOS验证码倒计时实现,退出进入以后继续倒计时

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

作者: 超zd | 来源:发表于2021-02-01 11:53 被阅读0次

需求

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

相关文章

  • iOS 短信验证码倒计时按钮的实现

    验证码倒计时按钮的应用是非常普遍的,本文介绍了IOS实现验证码倒计时功能,点击获取验证码,进入时间倒计时,感兴趣的...

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

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

  • iOS 短信验证码倒计时按钮的实现

    个人博客: LiCheng的博客 引言: 验证码倒计时按钮的应用是非常普遍的,本文介绍了IOS实现验证码倒计时功能...

  • iOS实现倒计时的三种方式

    iOS实现倒计时的三种方式 做iOS app开发的过程当中,经常会出现获取验证码等需求,这个时候一般会使用倒计时来...

  • 小程序如何实现验证码倒计时

    实现功能: 点击发送验证码,发送按钮进入倒计时状态。 具体实现: 要实现JS的倒计时功能,首先要了解两个JS的定...

  • 倒计时

    ios怎么在cell上添加倒计时 iOS中 简单易懂的秒杀倒计时/倒计时 iOS开发-三种倒计时的写法 iOS实现...

  • CountDownTimer

    CountDownTimer 倒计时帮助类内部实现原理是 Handler主要应用,手机验证码,倒计时 如果觉得文章...

  • iOS开发-使用GCD机制来实现倒计时功能

    使用GCD机制来实现倒计时功能。实现的是类似注册页面发送验证码的倒计时。 - (void)getCodeSucce...

  • iOS 短信验证码倒计时按钮

    级别: ★★☆☆☆标签:「iOS 验证码后台倒计时」「NSTimer后台运行」「iOS 定时器后台运行」作者: ...

  • 后台时不暂停的倒计时

    背景:注册页面或去验证码倒计时功能,如果进入后台后倒计时停止,产品希望即使进入后台后倒计时仍然不会停止。 定时器代...

网友评论

      本文标题:iOS验证码倒计时实现,退出进入以后继续倒计时

      本文链接:https://www.haomeiwen.com/subject/jbyjtltx.html