iOS开发造* | 优雅的封装一个倒计时button

点击上方“iOS开发”,选择“置顶公众号”

关键时刻,第一时间送达!

iOS开发造* | 优雅的封装一个倒计时button

iOS开发造* | 优雅的封装一个倒计时button


目标


封装一个满足基本功能并且方便使用的倒计时按钮,关键是:


iOS开发造* | 优雅的封装一个倒计时button

要优雅


需要实现的基本功能


以获取短信验证码为例。用户点击获取验证码按钮后,按钮enabled立即设置为NO,并且向后台发送请求,若请求失败,恢复按钮的enabled,反之开始倒计时,期间持续刷新按钮文本,倒计时结束后重置按钮。


需要处理的几个点


1.用户点击按钮


此时按钮的enabled立即变为NO,并向后台发起请求。


2.请求结束


若请求成功,开始倒计时,反之恢复按钮的可点状态并提示用户重试。


3.倒计时进行中


持续刷新按钮文本。


4.倒计时结束


恢复按钮的可点状态,重置按钮文本。


倒计时按钮封装


将上述几个需要处理的点以block的方式封装:


#import "CQCountDownButton.h"

#import <MSWeakTimer.h>


typedef void(^ButtonClickedBlock)();

typedef void(^CountDownStartBlock)();

typedef void(^CountDownUnderwayBlock)(NSInteger restCountDownNum);

typedef void(^CountDownCompletionBlock)();


@interface CQCountDownButton ()


/** 控制倒计时的timer */

@property (nonatomic, strong) MSWeakTimer *timer;

/** 按钮点击事件的回调 */

@property (nonatomic, copy) ButtonClickedBlock buttonClickedBlock;

/** 倒计时开始时的回调 */

@property (nonatomic, copy) CountDownStartBlock countDownStartBlock;

/** 倒计时进行中的回调(每秒一次) */

@property (nonatomic, copy) CountDownUnderwayBlock countDownUnderwayBlock;

/** 倒计时完成时的回调 */

@property (nonatomic, copy) CountDownCompletionBlock countDownCompletionBlock;


@end


@implementation CQCountDownButton {

    /** 倒计时开始值 */

    NSInteger _startCountDownNum;

    /** 剩余倒计时的值 */

    NSInteger _restCountDownNum;

}


/**

 构造方法

 

 @param frame frame

 @param duration 倒计时时间

 @param buttonClicked 按钮点击事件的回调

 @param countDownStart 倒计时开始时的回调

 @param countDownUnderway 倒计时进行中的回调(每秒一次)

 @param countDownCompletion 倒计时完成时的回调

 @return 倒计时button

 */

- (instancetype)initWithFrame:(CGRect)frame

                     duration:(NSInteger)duration

                buttonClicked:(void(^)())buttonClicked

               countDownStart:(void(^)())countDownStart

            countDownUnderway:(void(^)(NSInteger restCountDownNum))countDownUnderway

          countDownCompletion:(void(^)())countDownCompletion {

    if (self = [super initWithFrame:frame]) {

        _startCountDownNum = duration;

        self.buttonClickedBlock       = buttonClicked;

        self.countDownStartBlock      = countDownStart;

        self.countDownUnderwayBlock   = countDownUnderway;

        self.countDownCompletionBlock = countDownCompletion;

        [self addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];

    }

    return self;

}


/** 按钮点击 */

- (void)buttonClicked:(CQCountDownButton *)sender {

    sender.enabled = NO;

    self.buttonClickedBlock();

}


/** 开始倒计时 */

- (void)startCountDown {

    if (self.timer) {

        [self.timer invalidate];

        self.timer = nil;

    }

    _restCountDownNum = _startCountDownNum;

    self.countDownStartBlock(); // 调用倒计时开始的block

    self.timer = [MSWeakTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(refreshButton) userInfo:nil repeats:YES dispatchQueue:dispatch_get_main_queue()];

}


/** 刷新按钮内容 */

- (void)refreshButton {

    _restCountDownNum --;

    self.countDownUnderwayBlock(_restCountDownNum); // 调用倒计时进行中的回调

    if (_restCountDownNum == 0) {

        [self.timer invalidate];

        self.timer = nil;

        _restCountDownNum = _startCountDownNum;

        self.countDownCompletionBlock(); // 调用倒计时完成的回调

        self.enabled = YES;

    }

}


畅快使用,一个方法搞定所有事件处理及回调


__weak __typeof__(self) weakSelf = self;


self.countDownButton = [[CQCountDownButton alloc] initWithFrame:CGRectMake(90, 90, 150, 30) duration:10 buttonClicked:^{

    //------- 按钮点击 -------//

    [SVProgressHUD showWithStatus:@"正在获取验证码..."];

    // 请求数据

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

        int a = arc4random() % 2;

        if (a == 0) {

            // 获取成功

            [SVProgressHUD showSuccessWithStatus:@"验证码已发送"];

            // 获取到验证码后开始倒计时

            [weakSelf.countDownButton startCountDown];

        } else {

            // 获取失败

            [SVProgressHUD showErrorWithStatus:@"获取失败,请重试"];

            weakSelf.countDownButton.enabled = YES;

        }

    });

} countDownStart:^{

    //------- 倒计时开始 -------//

    NSLog(@"倒计时开始");

} countDownUnderway:^(NSInteger restCountDownNum) {

    //------- 倒计时进行中 -------//

    [weakSelf.countDownButton setTitle:[NSString stringWithFormat:@"再次获取(%ld秒)", restCountDownNum] forState:UIControlStateNormal];

} countDownCompletion:^{

    //------- 倒计时结束 -------//

    [weakSelf.countDownButton setTitle:@"点击获取验证码" forState:UIControlStateNormal];

    NSLog(@"倒计时结束");

}];


亮点


倒计时进行中的block,通过传递剩余倒计时数值,优雅实现按钮的持续更新:


countDownUnderway:^(NSInteger restCountDownNum) {

    //------- 倒计时进行中 -------//

    [weakSelf.countDownButton setTitle:[NSString stringWithFormat:@"再次获取(%ld秒)", restCountDownNum] forState:UIControlStateNormal];

 } 


Block or Delegate?


每当涉及到回调的时候我都会考虑这个问题。


block强调结果而delegate强调过程,在这里我们显然要的是结果。还有就是,如果这里采用delegate,代码的组织将更繁琐(需要4个代理方法依次对应4个block)。


但是,如果真的用delegate的话,用#pragma mark - count down将4个代理方法放在一起,想较block而言代码结构会更加的层次分明,这也是大家通常认为delegate更容易维护的原因之一吧。


使用block还是delegate这是一个仁者见仁智者见智的问题,我个人认为如果你对你的代码有较高要求、懂得换位思考,那么你不管使用delegate还是block都可以写出赏心悦目的代码,反之,都将惨不忍睹。



内存管理


因为涉及到timerblock,所以内存这一块要警惕。


Thread-safe NSTimer drop-in alternative that doesn't retain the target and supports being used with GCD queues.



关于block


注意使用weakSelf。


最重要的还是用instrument彻底的检查一下。

我已经用instrument检查多次了,请放心使用。


iOS开发造* | 优雅的封装一个倒计时button

给dalao递优秀三方库.gif


Demo


demo(https://github.com/CaiWanFeng/CQCountDownButton)



iOS开发造* | 优雅的封装一个倒计时button

  • 作者:无夜之星辰

  • 链接:http://www.jianshu.com/p/34e87194fb83

  • 來源:简书

  • iOS开发整理发布,转载请联系作者授权

iOS开发造* | 优雅的封装一个倒计时button

iOS开发造* | 优雅的封装一个倒计时button【点击成为安卓大神】