需求
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
网友评论