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);
}