我们设计页面点击的时候肯定会出现多种事件点击的情况,如网络请求验证点击,回调点击,通知点击等等。。。。这样也就可能会造成一种延迟(停顿)的反应,有些页面可以通过加上遮罩防重点击,但有些页面显然不合适,这中情况下控制按钮的点击频率就显得很有必要了。
举个例子模拟网络回调Push到下个页面,如果我们连续点击会出现什么情况?
//模拟网络请求延迟(0.5s)
__weak typeof(self)weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[weakSelf performSegueWithIdentifier:@"pushToTextVc" sender:self];
});
GIF演示 栈图连续点击按钮
从_UIModernBarButton - text 我看出栈中加入两层(push两次) HomeVc -> textVc - > textVc
解决办法:
- 按钮响应后让屏幕不可点击(HUD)
- 控制button的action做响应时间限制 (推荐)
这里我更推荐第二种,以上我只是举出一种很常见的情况,类似这样需要限定重复点击事件的在项目中还是很普遍得,所以第二种方法可以全局处理应用更多场景,而蒙板的使用会有一定限制。
解决:
如果说知道利用Runtime去做限制,那首先我们应该考虑怎么样去控制按钮的响应?那得简单介绍一下
Target-Action
机制。
- Target-Action机制 : 直译就是“目标-行为”机制,我们给按钮添加点击事件:
[self.touchClickButton addTarget:self action:@selector(buttonClick:) forControlEvents:UIControlEventTouchUpInside];
按钮的点击事件发生时,会将消息发送到target(此处即为self对象),并由target对象的tapButton:方法来处理相应的事件。但是当
sendAction:to:forEvent:
和addTarget:action:forControlEvents
最后处理事件的sendAction
。我们从这个方面入手利用Runtime在控制sendAction
。
- 推荐参考文章:UIKit: UIControl
设计:
从三个方面入手:
-
创建一个
UIButton
和分类方法如 - UIButton+DCHook -
利用Runtime去交换
sendAction:to:forEvent:
方法如换成 -dc_sendAction:to:forEvent:
-
添加一个外部属性可以自己控制按钮间隔的响应时间如 -
NSTimeInterval actionInterval
实现核心代码:
- UIButton+DCHook.h
@interface UIButton (DCHook)
#pragma mark - 响应时间的控制
@property (assign, nonatomic) NSTimeInterval actionInterval;
@end
- UIButton+DCHook.m
#import <objc/runtime.h>
@interface UIButton ()
@property (nonatomic, assign) BOOL ignoreEvent; // 是否忽视点击的事件
@end
#define defaultInterval 1.0 //默认时间间隔
static const char *UIControl_enventActionInterval = "UIControl_enventActionInterval";
static const char *UIControl_enventIsIgnoreEvent = "UIButton_EnventIsIgnoreEvent";
@implementation UIButton (DCHook)
#pragma mark - runtime 动态绑定 属性
- (void)setIsIgnoreEvent:(BOOL)isIgnoreEvent
{
objc_setAssociatedObject(self, UIControl_enventIsIgnoreEvent, @(isIgnoreEvent), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (BOOL)isIgnoreEvent{
return [objc_getAssociatedObject(self, UIControl_enventIsIgnoreEvent) boolValue];
}
- (void)setActionInterval:(NSTimeInterval)actionInterval
{
objc_setAssociatedObject(self, UIControl_enventActionInterval, @(actionInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSTimeInterval)actionInterval
{
return [objc_getAssociatedObject(self, UIControl_enventActionInterval) doubleValue];
}
#pragma mark - Load
+ (void)load
{
SEL selA = @selector(sendAction:to:forEvent:);
SEL selB = @selector(dc_sendAction:to:forEvent:);
Method methodA = class_getInstanceMethod([UIButton class],selA);
Method methodB = class_getInstanceMethod([self class], selB);
objc_setAssociatedObject([UIButton class], UIControl_enventIsIgnoreEvent, @(NO), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
BOOL isAdd = class_addMethod([UIButton class], selA, method_getImplementation(methodB), method_getTypeEncoding(methodB));
if (isAdd) {
class_replaceMethod([UIButton class], selB, method_getImplementation(methodA), method_getTypeEncoding(methodA));
}else{
//添加失败了 说明本类中有methodB的实现,此时只需要将methodA和methodB的IMP互换一下即可。
method_exchangeImplementations(methodA, methodB);
}
}
#pragma mark - 交换方法
- (void)dc_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event
{
NSTimeInterval interval = [objc_getAssociatedObject(self, UIControl_enventActionInterval) doubleValue];
if ([objc_getAssociatedObject([UIButton class], UIControl_enventIsIgnoreEvent) boolValue]){ //忽略直接返回
return;
}else if (defaultInterval > 0){ // 点击事件
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(((interval == 0) ? defaultInterval : interval) * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
objc_setAssociatedObject([UIButton class], UIControl_enventIsIgnoreEvent, @(NO), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
});
}
objc_setAssociatedObject([UIButton class], UIControl_enventIsIgnoreEvent, @(YES), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// 这里看上去会陷入递归调用死循环,但在运行期此dc_sendAction方法是和sendAction:to:forEvent:互换的,相当于执行sendAction:to:forEvent:方法,所以并不会陷入死循环。
[self dc_sendAction:action to:target forEvent:event];
}
@end
设置默认时间间隔为1s,可手动通过
actionInterval
更改如下
self.touchClickButton.actionInterval = 3; //测试更改为3s
打印响应图
这样就算你再怎么连续点击也不过造成重复响应的问题了
这里有在自定义中一个细节
#define defaultInterval 1.0 //默认时间间隔
NSTimeInterval interval = [objc_getAssociatedObject(self, UIControl_enventActionInterval) doubleValue];
(interval == 0) ? defaultInterval : interval;
我通过三目判断未设置属性时默认为1s间隔,那如果出现就需要重复点击的页面我们只需要把
actionInterval
设置为CGFLOAT_MIN
即可;
// CGFLOAT_MIN : Float类型的最小非负值非低于正常值
self.touchClickButton.actionInterval = CGFLOAT_MIN;
如果说在不需要间隔的情况下每次都要设置属性觉得麻烦,也可以创建一个DCNOIntervalButton父类,以后继承就好了。
网友评论