美文网首页
iOS UIButton倒计时、指示器、粒子效果

iOS UIButton倒计时、指示器、粒子效果

作者: 弹吉他的少年 | 来源:发表于2020-11-26 18:02 被阅读0次

    前言

    • 分享三款按钮来使用, 倒计时按钮,指示器按钮,点赞粒子效果按钮
    image

    倒计时按钮

    Property & API

    @interface UIButton (KJCountDown)
    /// 倒计时结束的回调
    @property(nonatomic,copy,readwrite)void(^kButtonCountDownStop)(void);
    /// 设置倒计时的间隔和倒计时文案,默认为 @"%zd秒"
    - (void)kj_startTime:(NSInteger)timeout CountDownFormat:(NSString*)format;
    /// 取消倒计时
    - (void)kj_cancelTimer;
    
    @end
    

    简单介绍

    正在倒计时的按钮是不可点击,内部主要就是声明一个计时器NSTimer来处理倒计时按钮,在计时期间关闭userInteractionEnabled属性

    1. kButtonCountDownStop

    倒计时结束时刻调用该回调

    2. kj_startTime:CountDownFormat:

    开始计时

    - (void)kj_startTime:(NSInteger)timeout CountDownFormat:(NSString*)format{
        [self kj_cancelTimer];
        self.timeOut = timeout;
        self.xxtitle = self.titleLabel.text;
        NSDictionary *info = @{@"countDownFormat":format ?: @"%zd秒"};
        self.timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerMethod:) userInfo:info repeats:YES];
        [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
        dispatch_async(dispatch_get_main_queue(), ^{
            [self setTitle:[NSString stringWithFormat:format ?: @"%zd秒",timeout] forState:UIControlStateNormal];
            self.userInteractionEnabled = NO;
        });
    }
    - (void)timerMethod:(NSTimer*)timer{
        NSDictionary *info = timer.userInfo;
        NSString *countDownFormat = info[@"countDownFormat"];
        if (self.timeOut <= 0){
            [self kj_cancelTimer];
        }else{
            self.timeOut--;
            dispatch_async(dispatch_get_main_queue(), ^{
                [self setTitle:[NSString stringWithFormat:countDownFormat,self.timeOut] forState:UIControlStateNormal];
                self.userInteractionEnabled = NO;
            });
        }
    }
    

    3. kj_cancelTimer

    取消倒计时

    - (void)kj_cancelTimer{
        if (self.timer == nil) return;
        [self.timer invalidate];
        self.timer = nil;
        dispatch_async(dispatch_get_main_queue(), ^{
            [self setTitle:self.xxtitle forState:UIControlStateNormal];
            self.userInteractionEnabled = YES;
            if (self.kButtonCountDownStop) { self.kButtonCountDownStop(); }
        });
    }
    

    内部Property

    - (NSTimer*)timer{
        return objc_getAssociatedObject(self, @selector(timer));
    }
    - (void)setTimer:(NSTimer*)timer{
        objc_setAssociatedObject(self, @selector(timer), timer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    - (NSString*)xxtitle{
        return objc_getAssociatedObject(self, @selector(xxtitle));
    }
    - (void)setXxtitle:(NSString*)xxtitle{
        objc_setAssociatedObject(self, @selector(xxtitle), xxtitle, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    - (void)setKButtonCountDownStop:(void(^)(void))kButtonCountDownStop{
        objc_setAssociatedObject(self, @selector(kButtonCountDownStop), kButtonCountDownStop, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    - (void(^)(void))kButtonCountDownStop{
        return objc_getAssociatedObject(self, @selector(kButtonCountDownStop));
    }
    - (NSInteger)timeOut{
        return [objc_getAssociatedObject(self, @selector(timeOut)) integerValue];
    }
    - (void)setTimeOut:(NSInteger)timeOut{
        objc_setAssociatedObject(self, @selector(timeOut), @(timeOut), OBJC_ASSOCIATION_ASSIGN);
    }
    

    使用示例

    [_countDownButton kj_addAction:^(UIButton * _Nonnull kButton) {
        [kButton kj_startTime:6 CountDownFormat:@"计时%zd秒"];
    }];
    _countDownButton.kButtonCountDownStop = ^{
        NSLog(@"计时结束!!!");
    };
    

    指示器按钮

    Property & API

    @interface UIButton (KJIndicator)
    /// 按钮是否正在提交中
    @property(nonatomic,assign,readonly)bool submitting;
    /// 指示器和文字间隔,默认5px
    @property(nonatomic,assign)CGFloat indicatorSpace;
    /// 指示器颜色,默认白色
    @property(nonatomic,assign)UIActivityIndicatorViewStyle indicatorType;
    
    /// 开始提交,指示器跟随文字
    - (void)kj_beginSubmitting:(NSString*)title;
    /// 结束提交
    - (void)kj_endSubmitting;
    /// 显示指示器
    - (void)kj_showIndicator;
    /// 隐藏指示器
    - (void)kj_hideIndicator;
    
    @end
    

    简单介绍

    其实就是在按钮内部放文本UILabel和指示器UIActivityIndicatorView

    内部出了

    #import "UIButton+KJIndicator.h"
    #import <objc/runtime.h>
    
    @implementation UIButton (KJIndicator)
    static NSString *kIndicatorLastTitle = nil;
    - (void)kj_beginSubmitting:(NSString*)title{
        [self kj_endSubmitting];
        kSubmitting = true;
        kIndicatorLastTitle = self.titleLabel.text;
        self.enabled = NO;
        [self setTitle:@"" forState:UIControlStateNormal];
        
        self.indicatorType = self.indicatorType?:UIActivityIndicatorViewStyleWhite;
        self.indicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:self.indicatorType];
        [self addSubview:self.indicatorView];
        
        self.indicatorSpace = self.indicatorSpace?:5;
        CGFloat w = self.bounds.size.width;
        CGFloat h = self.bounds.size.height;
        CGFloat sp = w / 2.;
        if (![title isEqualToString:@""]) {
            self.indicatorLabel = [[UILabel alloc] init];
            self.indicatorLabel.text = title;
            self.indicatorLabel.font = self.titleLabel.font;
            self.indicatorLabel.textColor = self.titleLabel.textColor;
            [self addSubview:self.indicatorLabel];
            
            CGSize size = [title boundingRectWithSize:CGSizeMake(MAXFLOAT,0.0) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:self.titleLabel.font} context:nil].size;
            sp = ((w-self.indicatorSpace-size.width)*.5)?:0.0;
            self.indicatorLabel.frame = CGRectMake(sp+self.indicatorSpace+self.indicatorView.frame.size.width/2, 0, size.width, h);
        }
        
        self.indicatorView.center = CGPointMake(sp, h/2);
        [self.indicatorView startAnimating];
    }
    
    - (void)kj_endSubmitting {
        [self kj_hideIndicator];
        self.indicatorView = nil;
        self.indicatorLabel = nil;
    }
    
    - (void)kj_showIndicator {
        if (self.indicatorView && self.indicatorView.superview == nil) {
            [self addSubview:self.indicatorView];
            [self.indicatorView startAnimating];
        }
        if (self.indicatorLabel && self.indicatorLabel.superview == nil) {
            [self addSubview:self.indicatorLabel];
            [self setTitle:@"" forState:UIControlStateNormal];
        }
    }
    
    - (void)kj_hideIndicator {
        kSubmitting = false;
        self.enabled = YES;
        
        [self.indicatorView removeFromSuperview];
        [self.indicatorLabel removeFromSuperview];
        
        if (self.indicatorLabel) {
            [self setTitle:kIndicatorLastTitle forState:UIControlStateNormal];
        }
        if (self.indicatorView) {
            [self.indicatorView stopAnimating];
            [self setTitle:kIndicatorLastTitle forState:UIControlStateNormal];
        }
    }
    
    #pragma mark - getter/setter
    static bool kSubmitting = false;
    - (bool)submitting{
        return kSubmitting;
    }
    - (CGFloat)indicatorSpace{
        return [objc_getAssociatedObject(self, @selector(indicatorSpace)) floatValue];
    }
    - (void)setIndicatorSpace:(CGFloat)indicatorSpace{
        objc_setAssociatedObject(self, @selector(indicatorSpace), @(indicatorSpace), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    - (UIActivityIndicatorViewStyle)indicatorType{
        return (UIActivityIndicatorViewStyle)[objc_getAssociatedObject(self, @selector(indicatorType)) intValue];
    }
    - (void)setIndicatorType:(UIActivityIndicatorViewStyle)indicatorType{
        objc_setAssociatedObject(self, @selector(indicatorType), @(indicatorType), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    - (UIActivityIndicatorView*)indicatorView{
        return objc_getAssociatedObject(self, @selector(indicatorView));
    }
    - (void)setIndicatorView:(UIActivityIndicatorView*)indicatorView{
        objc_setAssociatedObject(self, @selector(indicatorView), indicatorView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    - (UILabel*)indicatorLabel{
        return objc_getAssociatedObject(self, @selector(indicatorLabel));
    }
    - (void)setIndicatorLabel:(UILabel*)indicatorLabel{
        objc_setAssociatedObject(self, @selector(indicatorLabel), indicatorLabel, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    @end
    

    使用示例

    /// 开启指示器
    [button kj_addAction:^(UIButton * _Nonnull kButton) {
        [kButton kj_beginSubmitting:@"测试ing"];
    }];
    
    [button kj_addAction:^(UIButton * _Nonnull kButton) {
        kButton.selected = !kButton.selected;
        if (kButton.selected) {
            [weakself.submitButton kj_hideIndicator];
        }else{
            [weakself.submitButton kj_showIndicator];
        }
    }];
    

    粒子效果

    Property & API

    @interface UIButton (KJEmitter)
    /// 粒子,备注 name 属性不要更改
    @property(nonatomic,strong,readonly)CAEmitterCell *emitterCell;
    /// 设置粒子效果
    - (void)kj_buttonSetEmitterImage:(UIImage*_Nullable)image OpenEmitter:(bool)open;
    
    @end
    

    简单介绍

    其实就是在setSelected之后去处理粒子效果

    kj_buttonSetEmitterImage:

    初始化效果,获取粒子图,设置CAEmitterLayer

    - (void)kj_buttonSetEmitterImage:(UIImage*_Nullable)image OpenEmitter:(bool)open{
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            method_exchangeImplementations(class_getInstanceMethod(self.class, @selector(setSelected:)), class_getInstanceMethod(self.class, @selector(kj_setSelected:)));
        });
        self.emitterImage = image?:[UIImage imageNamed:@"KJKit.bundle/button_sparkle"];
        self.emitterOpen = open;
        [self setupLayer];
    }
    

    设置粒子效果的相关参数,考虑到自定义emitterImage的情况,所以还是把粒子emitterCell开放出去,这样也方便外界修改对应的参数(备注:name属性不要修改)

    - (void)setupLayer{
        CAEmitterCell *emitterCell = [CAEmitterCell emitterCell];
        emitterCell.name = @"name";
        emitterCell.alphaRange = 0.10;
        emitterCell.lifetime = 0.7;
        emitterCell.lifetimeRange = 0.3;
        emitterCell.velocity = 40.00;
        emitterCell.velocityRange = 10.00;
        emitterCell.scale = 0.04;
        emitterCell.scaleRange = 0.02;
        emitterCell.contents = (id)self.emitterImage.CGImage;
        self.emitterCell = emitterCell;
        
        CAEmitterLayer *emitterLayer = [CAEmitterLayer layer];
        emitterLayer.name = @"emitterLayer";
        emitterLayer.emitterShape = kCAEmitterLayerCircle;
        emitterLayer.emitterMode = kCAEmitterLayerOutline;
        emitterLayer.emitterSize = CGSizeMake(10, 0);
        emitterLayer.emitterCells = @[emitterCell];
        emitterLayer.renderMode = kCAEmitterLayerOldestFirst;
        emitterLayer.position = CGPointMake(self.frame.size.width/2.0, self.frame.size.height/2.0);
        emitterLayer.zPosition = -1;
        [self.layer addSublayer:emitterLayer];
        self.explosionLayer = emitterLayer;
    }
    

    开启粒子喷射和缩放效果

    - (void)buttonAnimation{
        CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"];
        if (self.selected) {
            animation.values = @[@1.5 ,@0.8, @1.0,@1.2,@1.0];
            animation.duration = 0.4;
            /// 开始喷射
            self.explosionLayer.beginTime = CACurrentMediaTime();
            [self.explosionLayer setValue:@2000 forKeyPath:@"emitterCells.name.birthRate"];
            [self performSelector:@selector(stop) withObject:nil afterDelay:0.2];
        }else{
            animation.values = @[@0.8, @1.0];
            animation.duration = 0.2;
        }
        animation.calculationMode = kCAAnimationCubic;
        [self.layer addAnimation:animation forKey:@"transform.scale"];
    }
    

    0.2秒之后停止喷射

    [self performSelector:@selector(stop) withObject:nil afterDelay:0.2];
    

    停止喷射,其实就是将粒子的生命周期设置为零

    - (void)stop {
        [self.explosionLayer setValue:@0 forKeyPath:@"emitterCells.name.birthRate"];
    }
    

    附上完整代码

    #import "UIButton+KJEmitter.h"
    #import <objc/runtime.h>
    
    @implementation UIButton (KJEmitter)
    /// 设置粒子效果
    - (void)kj_buttonSetEmitterImage:(UIImage*_Nullable)image OpenEmitter:(bool)open{
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            method_exchangeImplementations(class_getInstanceMethod(self.class, @selector(setSelected:)), class_getInstanceMethod(self.class, @selector(kj_setSelected:)));
        });
        self.emitterImage = image?:[UIImage imageNamed:@"KJKit.bundle/button_sparkle"];
        self.emitterOpen = open;
        [self setupLayer];
    }
    /// 方法交换
    - (void)kj_setSelected:(BOOL)selected{
        [self kj_setSelected:selected];
        if (self.emitterOpen) [self buttonAnimation];
    }
    
    - (UIImage*)emitterImage{
        return objc_getAssociatedObject(self, @selector(emitterImage));
    }
    - (void)setEmitterImage:(UIImage*)emitterImage{
        objc_setAssociatedObject(self, @selector(emitterImage), emitterImage, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    - (BOOL)emitterOpen{
        return [objc_getAssociatedObject(self, @selector(emitterOpen)) intValue];
    }
    - (void)setEmitterOpen:(BOOL)emitterOpen{
        objc_setAssociatedObject(self, @selector(emitterOpen), @(emitterOpen), OBJC_ASSOCIATION_ASSIGN);
    }
    - (CAEmitterLayer*)explosionLayer{
        return objc_getAssociatedObject(self, @selector(explosionLayer));
    }
    - (void)setExplosionLayer:(CAEmitterLayer *)explosionLayer{
        objc_setAssociatedObject(self, @selector(explosionLayer), explosionLayer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    - (CAEmitterCell*)emitterCell{
        return objc_getAssociatedObject(self, @selector(emitterCell));
    }
    - (void)setEmitterCell:(CAEmitterCell*)emitterCell{
        objc_setAssociatedObject(self, @selector(emitterCell), emitterCell, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    #pragma mark - 粒子效果相关
    - (void)setupLayer{
        CAEmitterCell *emitterCell = [CAEmitterCell emitterCell];
        emitterCell.name = @"name";
        emitterCell.alphaRange = 0.10;
        emitterCell.lifetime = 0.7;
        emitterCell.lifetimeRange = 0.3;
        emitterCell.velocity = 40.00;
        emitterCell.velocityRange = 10.00;
        emitterCell.scale = 0.04;
        emitterCell.scaleRange = 0.02;
        emitterCell.contents = (id)self.emitterImage.CGImage;
        self.emitterCell = emitterCell;
        
        CAEmitterLayer *emitterLayer = [CAEmitterLayer layer];
        emitterLayer.name = @"emitterLayer";
        emitterLayer.emitterShape = kCAEmitterLayerCircle;
        emitterLayer.emitterMode = kCAEmitterLayerOutline;
        emitterLayer.emitterSize = CGSizeMake(10, 0);
        emitterLayer.emitterCells = @[emitterCell];
        emitterLayer.renderMode = kCAEmitterLayerOldestFirst;
        emitterLayer.position = CGPointMake(self.frame.size.width/2.0, self.frame.size.height/2.0);
        emitterLayer.zPosition = -1;
        [self.layer addSublayer:emitterLayer];
        self.explosionLayer = emitterLayer;
    }
    /// 开始动画
    - (void)buttonAnimation{
        CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"];
        if (self.selected) {
            animation.values = @[@1.5 ,@0.8, @1.0,@1.2,@1.0];
            animation.duration = 0.4;
            self.explosionLayer.beginTime = CACurrentMediaTime();
            [self.explosionLayer setValue:@2000 forKeyPath:@"emitterCells.name.birthRate"];
            [self performSelector:@selector(stop) withObject:nil afterDelay:0.2];
        }else{
            animation.values = @[@0.8, @1.0];
            animation.duration = 0.2;
        }
        animation.calculationMode = kCAAnimationCubic;
        [self.layer addAnimation:animation forKey:@"transform.scale"];
    }
    /// 停止喷射
    - (void)stop {
        [self.explosionLayer setValue:@0 forKeyPath:@"emitterCells.name.birthRate"];
    }
    
    @end
    

    到此三种按钮就介绍完毕,有空再补充完善,码字累死我了- -|

    备注:本文用到的部分函数方法和Demo,均来自三方库KJEmitterView,如有需要的朋友可自行pod 'KJEmitterView'引入即可

    倒计时、指示器、粒子效果介绍就到此完毕,后面有相关再补充,写文章不容易,还请点个小星星传送门

    相关文章

      网友评论

          本文标题:iOS UIButton倒计时、指示器、粒子效果

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