Is life always this hard,or is it just when you are a kid?
Always like this.
在软件开发项目中,我们经常会碰到点击按钮发送网络请求,或者点击按钮进行页面之间的逻辑跳转。但是有时候遇到一些卡顿的话,用户可能会失去耐心,然后疯狂的点击,这时候就会多次调用按钮触发的方法。在某些特定的情景下会导致页面重复push或者重复发送网络请求。这样的问题既对用户体验有影响,而且还会一定程度上增加服务器的压力。
下面详细讲解几种解决思路,有不完善的地方 希望大家能够纠正。
1.在按钮的触发方法内部做处理
首先创建一个按钮button
@interface ViewController ()
@property(nonatomic,strong)UIButton *button;
@end
- (void)viewDidLoad {
[super viewDidLoad];
self.button = [[UIButton alloc] initWithFrame:CGRectMake(200, 200, 100, 100)];
self.button.backgroundColor = [UIColor redColor];
[self.button addTarget:self action:@selector(btnClickedOperations:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.button];
}
- (void)btnClickedOperations:(id)sender{
static NSTimeInterval time = 0.0;
NSTimeInterval currentTime = [NSDate date].timeIntervalSince1970;
//限制用户点击按钮的时间间隔大于1秒钟
if (currentTime - time > 1) {
//处理逻辑
NSLog(@"这是一个测试");
}
time = currentTime;
}
最终只有用户的点击时间间隔超过一秒钟 才会再次调用你要处理的逻辑代码。这样就实现了避免用户连续点击按钮带来的影响。
image2.同样在方法内部做操作
在按钮方法内部对按钮的状态进行控制 在执行完指定的操作后 用户才可以继续点击按钮 同样可以避免连续点击带来的问题 这种方法适合用在处理逻辑时间比较久的情况 ,如果处理逻辑时间很短暂 那么就起不到限制用户连续点击的情况
- (void)test:(UIButton *)btn{
btn.enabled = NO;
//处理逻辑
btn.enabled = YES;
}
如果想控制按钮的时间间隔同样可以加一个延迟的方法
- (void)test:(UIButton *)btn{
btn.enabled = NO;
//处理逻辑
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"btnClickedOperations");
btn.enabled = YES;
});
}
实现效果
[图片上传中...(屏幕快照 2017-11-16 上午10.15.17.png-86dc45-1510798532321-0)]
3.重写button内部的 - (void)sendAction:(SEL)action to:(nullable id)target forEvent:(nullable UIEvent *)event;方法
只适合新项目,不太适合老项目,用的地方太多了
分析
iOS中的按钮事件机制 >>> Target-Action机制
用户点击时,产生一个按钮点击事件消息
这个消息发送给注册的Target处理
Target接收到消息,然后查找自己的SEL对应的具体实现IMP正儿八经的去处理点击事件
实际上该点击消息包含三个东西:
Target处理者
SEL方法Id
按钮事件当时触发时的状态
具体实现
@interface MyButton : UIButton
/**
按钮点击的间隔时间
*/
@property(nonatomic,assign)NSTimeInterval time;
@end
.m文件
#import "MyButton.h"
static const NSTimeInterval defaultDuration = 3.0f;
//记录按钮是否忽略按钮点击事件,默认第一次执行事件
static BOOL _isIgnoreEvent = NO;
/**
设置执行按钮事件状态
*/
static void resetState (){
_isIgnoreEvent = NO;
}
@interface MyButton()
@end
@implementation MyButton
- (void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event{
//1.按钮点击间隔事件
_time = _time = (0) ? defaultDuration :_time;
//2.是否忽略按钮点击事件
if (_isIgnoreEvent) {
// 2.1忽略按钮点击事件
// 2.1忽略此事件
return;
}else if (_time >0){
//不要忽略按钮的点击事件
//后续在事件间隔内直接忽略掉按钮事件
_isIgnoreEvent = YES;
//间隔事件后 执行按钮事件
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_time * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
resetState();
});
//发送按钮点击消息
[super sendAction:action to:target forEvent:event];
}
}
@end
实现效果
image4使用UIButton Category封装防止按钮连续点击的具体实现
分析:
其实大体上逻辑和上面的实现差不多,只是因为在Category分类里面,无法完成重写sendAction:to:forEvent:对应的实现,只能通过运行时替换掉sendAction:to:forEvent:具体实现之后拦截到UIButton的sendAction:to:forEvent:方式执行时,将上面例子的逻辑加进来.
具体实现代码
#import <UIKit/UIKit.h>
@interface UIButton (Tool)
/**
* 按钮点击的间隔时间
*/
@property (nonatomic, assign) NSTimeInterval clickDurationTime;
@end
#import "UIButton+Tool.h"
#import <objc/runtime.h>
// 默认的按钮点击时间
static const NSTimeInterval defaultDuration = 3.0f;
// 记录是否忽略按钮点击事件,默认第一次执行事件
static BOOL _isIgnoreEvent = NO;
// 设置执行按钮事件状态
static void resetState() {
_isIgnoreEvent = NO;
}
@implementation UIButton (Tool)
+ (void)load {
SEL originSEL = @selector(sendAction:to:forEvent:);
SEL mySEL = @selector(my_sendAction:to:forEvent:);
Method originM = class_getInstanceMethod([self class], originSEL);
const char *typeEncodinds = method_getTypeEncoding(originM);
Method newM = class_getInstanceMethod([self class], mySEL);
IMP newIMP = method_getImplementation(newM);
if (class_addMethod([self class], mySEL, newIMP, typeEncodinds)) {
class_replaceMethod([self class], originSEL, newIMP, typeEncodinds);
} else {
method_exchangeImplementations(originM, newM);
}
}
- (void)my_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
// 保险起见,判断下Class类型
if ([self isKindOfClass:[UIButton class]]) {
//1\. 按钮点击间隔事件
self.clickDurationTime = self.clickDurationTime == 0 ? defaultDuration : self.clickDurationTime;
//2\. 是否忽略按钮点击事件
if (_isIgnoreEvent) {
//2.1 忽略按钮事件
return;
} else if(self.clickDurationTime > 0) {
//2.2 不忽略按钮事件
// 后续在间隔时间内直接忽略按钮事件
_isIgnoreEvent = YES;
// 间隔事件后,执行按钮事件
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.clickDurationTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
resetState();
});
// 发送按钮点击消息
[self my_sendAction:action to:target forEvent:event];
}
} else {
[self my_sendAction:action to:target forEvent:event];
}
}
#pragma mark - associate
//由于分类不能增加属性 所以需要使用运行时动态绑定属性
- (void)setClickDurationTime:(NSTimeInterval)clickDurationTime {
objc_setAssociatedObject(self, @selector(clickDurationTime), @(clickDurationTime), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSTimeInterval)clickDurationTime {
return [objc_getAssociatedObject(self, @selector(clickDurationTime)) doubleValue];
}
我们的按钮类不需要做任何的事情,完全不知道被拦截附加完成了防止连续点击的逻辑.
使用
UIButton *customeBut = [[UIButton alloc] initWithFrame:CGRectMake(300, 400, 100, 100)];
customeBut.backgroundColor = [UIColor greenColor];
//设置间隔为4秒。
customeBut.clickDurationTime = 4.0f;
[customeBut addTarget:self action:@selector(customeButOperations:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:customeBut];
- (void)customeButOperations:(UIButton *)sender{
NSLog(@"%@", NSStringFromSelector(_cmd));
}
实现效果
image
网友评论