UIButton 防止重复点击
引言:
在日常开发中很容易遇到的问题就是 按钮的重复快速点击造成的一系列问题,下面就 防止按钮重复点击 列出了几种解决方案;
# 一. 禁止交互
使用 UIButton 的 userInteractionEnabled
属性。在点击后,禁止UIButton的交互,直到完成指定任务之后再将其 button 的交互打开即可。
-(void)actionFixMultiClick_enabled:(UIButton *)sender {
sender.userInteractionEnabled = NO;
[self btnClickedOperations];
}
- (void)btnClickedOperations {
self.view.backgroundColor = [UIColor redColor];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
btn.userInteractionEnabled = YES;
});
}
# 二. 延迟执行
使用 performSelector:withObject:afterDelay:
和cancelPreviousPerformRequestsWithTarget
使用这种方式点击 UIButton 的时候,自动取消掉之前的操作,延时1s后执行实际的操作。**重复点击时,每点击一次都会取消掉上一次点击要执行的函数,然后1秒后执行实际的函数。**这种方式看起来会比第一种和第三种稍微延迟执行实际的操作。
[button addTarget:self action:@selector(actionFixMultiClick_performSelector:) forControlEvents:(UIControlEventTouchUpInside)];
/** 按钮点击事件 */
- (void)actionFixMultiClick_performSelector:(UIButton *)sender {
// 取消执行btnClickedOperations函数
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(btnClickedOperations) object:nil];
// 延迟1秒执行btnClickedOperations函数
[self performSelector:@selector(btnClickedOperations) withObject:nil afterDelay:1];
}
/** 实际要操作的事件 */
-(void)btnClickedOperations{
}
# 三. Runtime
使用runtime来对sendAction:to:forEvent:
方法进行hook. UIControl的sendAction:to:forEvent:
方法用于处理事件响应.
如果我们在该方法的实现中,添加针对点击事件的时间间隔相关的处理代码,则能够做到在指定时间间隔中防止重复点击。
# 1. 自定义一个UIControl的分类:
UIControl+Custom.h:
#import <UIKit/UIKit.h>
@interface UIControl (Custom)
/** 添加点击事件的时间间隔 */
@property (nonatomic, assign) NSTimeInterval lc_acceptEventInterval;
/** 是否忽略点击事件,不响应点击事件 */
@property (nonatomic, assign) BOOL lc_ignoreEvent;
@end
Category不能给类添加属性,所以上面的两个属性只会有对应的getter和setter方法,不会添加真正的成员变量。
如果我们不在实现文件中添加其getter和setter方法,使用 btn.lc_acceptEventInterval = 1;
这种方法访问该属性会出错。
UIControl+Custom.m:
#import "UIControl+Custom.h"
#import <objc/runtime.h>
@implementation UIControl (Custom)
// 自己添加的时间间隔属性
static const char *UIControl_acceptEventInterval = "UIControl_acceptEventInterval";
// 自己添加的是否忽略点击事件属性
static const char *UIcontrol_ignoreEvent = "UIcontrol_ignoreEvent";
+(void)load{
// 将按钮默认的点击事件 替换为 自定义的点击事件
Method a = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:)); // 默认点击事件
Method b = class_getInstanceMethod(self, @selector(lc_sendAction:to:forEvent:)); // 自定义点击事件
method_exchangeImplementations(a, b);
}
/**
* 自定义点击事件
*/
-(void)lc_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event{
// 如果忽略点击, 直接返回
if (self.lc_ignoreEvent){
return;
}else{
// 如果延时时间 > 0
if (self.lc_acceptEventInterval > 0) {
// 忽略其他点击事件
self.lc_ignoreEvent = YES;
// 几秒后 设置按钮不忽略点击事件
[self performSelector:@selector(setLc_ignoreEvent:) withObject:@(NO) afterDelay:self.lc_acceptEventInterval];
}
}
[self lc_sendAction:action to:target forEvent:event];
}
-(NSTimeInterval)lc_acceptEventInterval{
// 动态的向类中获取 自定义的 UIControl_acceptEventInterval 方法
return [objc_getAssociatedObject(self, UIControl_acceptEventInterval) doubleValue];
}
-(void)setLc_acceptEventInterval:(NSTimeInterval)lc_acceptEventInterval{
// 动态的向类中添加 自定义的 UIControl_acceptEventInterval 方法
objc_setAssociatedObject(self, UIControl_acceptEventInterval, @(lc_acceptEventInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(BOOL)lc_ignoreEvent{
return [objc_getAssociatedObject(self, UIcontrol_ignoreEvent) boolValue];
}
-(void)setLc_ignoreEvent:(BOOL)lc_ignoreEvent{
objc_setAssociatedObject(self, UIcontrol_ignoreEvent, @(lc_ignoreEvent), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}