前言
分享三款按钮来使用, 倒计时按钮,指示器按钮,点赞粒子效果按钮
倒计时按钮
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