iOS驗(yàn)證碼倒計(jì)時(shí)實(shí)現(xiàn),退出進(jìn)入以后繼續(xù)倒計(jì)時(shí)

需求

App中有很多頁面地方要發(fā)送驗(yàn)證碼,涉及到驗(yàn)證碼的地方肯定會(huì)有倒計(jì)時(shí)功能。產(chǎn)品要求發(fā)送驗(yàn)證碼以后,在倒計(jì)時(shí)結(jié)束之前不重復(fù)發(fā)送驗(yàn)證碼。

第一步

首先實(shí)現(xiàn)倒計(jì)時(shí)功能,以登錄界面為例,用戶輸入手機(jī)號(hào)以后,需要點(diǎn)擊按鈕發(fā)送驗(yàn)證碼,發(fā)送驗(yàn)證碼成功以后,會(huì)調(diào)用下面方法,實(shí)現(xiàn)按鈕倒計(jì)時(shí)功能

- (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(), ^{
                //倒計(jì)時(shí)結(jié)束,改變按鈕狀態(tài),做相關(guān)操作
                [self.meeageButton setTitle:@"重新獲取" forState:UIControlStateNormal];
                [self.meeageButton setEnabled:YES];
            });
        }
        else {
            dispatch_async(dispatch_get_main_queue(), ^{
                //倒計(jì)時(shí)中 interval是倒計(jì)時(shí)描述,可以用此值更新按鈕文字
                [self.meeageButton setTitle:@(interval).stringValue forState:UIControlStateNormal];
                [self.meeageButton setEnabled:NO];
            });
        }
    });
    dispatch_resume(_timer);
}

上面方法實(shí)現(xiàn)了按鈕的倒計(jì)時(shí)功能,但是有個(gè)問題,如果不點(diǎn)擊返回按鈕,離開當(dāng)前頁面的話,那么倒計(jì)時(shí)正常,但是當(dāng)返回上層界面,再次進(jìn)入本頁面后,倒計(jì)時(shí)按鈕會(huì)重置。用戶可以重新發(fā)送驗(yàn)證碼,但是上次的倒計(jì)時(shí)時(shí)間還未到,這與產(chǎn)品需求不符合,所以上面的方案需要調(diào)整。

第二步

經(jīng)過思考以后,決定將倒計(jì)時(shí)功能單獨(dú)封裝到一個(gè)類中,避免頻繁書寫重復(fù)代碼。

1.新建BOUTimerManager類,繼承自NSObject

由于有多個(gè)頁面需要實(shí)現(xiàn)倒計(jì)時(shí)功能,為了區(qū)分倒計(jì)時(shí)所屬頁面,定義以下枚舉類型:

typedef NS_ENUM(NSInteger, BOUCountDownType) {
    BOUCountDownTypeLogin,//登錄界面
    BOUCountDownTypeFindPassword,//忘記密碼界面
    BOUCountDownTypeRegister,//注冊界面
    BOUCountDownTypeModifyPhone,//修改手機(jī)號(hào)界面
};

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

+ (instancetype)shareInstance;//此方法實(shí)現(xiàn)單例

- (void)timerCountDownWithType:(BOUCountDownType)countDownType;//調(diào)用此方法開始倒計(jì)時(shí),根據(jù)傳入的type值判斷開始哪個(gè)頁面的倒計(jì)時(shí)。

- (void)cancelTimerWithType:(BOUCountDownType)countDownType;//調(diào)用此方法取消倒計(jì)時(shí),根據(jù)傳入的type值判斷取消的是哪個(gè)頁面的倒計(jì)時(shí)。

在倒計(jì)時(shí)過程中,響應(yīng)界面需要根據(jù)是倒計(jì)時(shí)中或者倒計(jì)時(shí)完成處理相關(guān)頁面邏輯,我在這里使用發(fā)送通知的方法,在倒計(jì)時(shí)過程中和倒計(jì)時(shí)完成時(shí)發(fā)送通知,頁面注冊通知以后可以接收到倒計(jì)時(shí)狀態(tài),所以在BOUTimerManager.h還需定義以下內(nèi)容:

#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全部內(nèi)容如下:
#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實(shí)現(xiàn)全部內(nèi)容如下:
#import "BOUTimerManager.h"

#define kMaxCountDownTime           60//倒計(jì)時(shí)時(shí)間,可自定義

@interface BOUTimerManager ()

@property (nonatomic, assign) BOUCountDownType countDonwnType;

@property (nonatomic, nullable, strong) dispatch_source_t loginTimer;//登錄界面倒計(jì)時(shí)timer

@property (nonatomic, nullable, strong) dispatch_source_t findPwdTimer;//找回密碼界面倒計(jì)時(shí)timer

@property (nonatomic, nullable, strong) dispatch_source_t registerTimer;//注冊界面倒計(jì)時(shí)timer

@property (nonatomic, nullable, strong) dispatch_source_t modifyPhoneTimer;//修改手機(jī)號(hào)界面倒計(jì)時(shí)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是單例實(shí)現(xiàn)的宏定義

#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方法中注冊倒計(jì)時(shí)通知事件

[[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 處理倒計(jì)時(shí)事件

- (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:@"獲取驗(yàn)證碼" 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];
}

輸入手機(jī)號(hào)碼以后,點(diǎn)擊發(fā)送驗(yàn)證碼,后臺(tái)接口返回成功以后,開始倒計(jì)時(shí)

- (void)sendSMSRequestWithPhone:(NSString *)phoneNum sender:(UIButton *)sender {
    //模擬網(wǎng)絡(luò)請求
    //...
    //開始倒計(jì)時(shí)
    [[BOUTimerManager sharedInstance] timerCountDownWithType:BOUCountDownTypeLogin];
}

點(diǎn)擊登錄按鈕,后臺(tái)接口返回以后,取消登錄界面驗(yàn)證碼倒計(jì)時(shí)

- (void)clickLoginAction:(UIButton *)sender {
    //模擬網(wǎng)絡(luò)請求
    //...
    //取消登錄界面倒計(jì)時(shí)
    [[BOUTimerManager sharedInstance] cancelTimerWithType:BOUCountDownTypeLogin];
}

結(jié)尾

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容