美文网首页
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验证码倒计时实现,退出进入以后继续倒计时

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