iOS UIButton倒计时、指示器、粒子效果
2020-11-26 本文已影响0人
弹吉他的少年
前言
- 分享三款按钮来使用, 倒计时按钮,指示器按钮,点赞粒子效果按钮
倒计时按钮
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