Light's Blog

The best or nothing.

Prevent Duplicate Clicks

| Comments

在应用开发过程中,点击事件为耗时操作或者延时响应,例如请求服务器数据,push至下一个界面,如果不处理用户重复点击事件,将会重复触发事件。下面介绍几种简单的处理方法。

Button

点击后button状态置为disabled; 操作完成后button状态置为enabled;

View

根据处理状态设置userInteractiveEnable。

Push

方法一: 点击后button状态置为disabled; viewDidDisappear置为enabled;

方法二:在push前添加判断:如果和上一个视图控制器一样,隔绝此次操作。

if ([self.navigationController.topViewController isKindOfClass:[MyViewController class]]) {
    return;
}

时间监听类

用一个静态变量记录上一次点击的时间,每次点击check时间间隔是否达到要求。

Runtime

使用Runtime监听点击事件,忽略重复点击。 添加一个eventTimeInterval属性,使其规定时间内只能响应一次点击事件。

参考iOS防止重复点击button

.h

1
2
3
4
5
6
7
8
9
10
#import <UIKit/UIKit.h>

@interface UIButton (WXD)

/**
*  为按钮添加点击间隔 eventTimeInterval秒
*/
@property (nonatomic, assign) NSTimeInterval eventTimeInterval;

@end

.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#import "UIButton+WXD.h"
#import <objc/runtime.h>
#define defaultInterval 1  //默认时间间隔

@interface UIButton ()

/**
*  bool YES 忽略点击事件   NO 允许点击事件
*/
@property (nonatomic, assign) BOOL isIgnoreEvent;

@end

@implementation UIButton (WXD)

static const char *UIControl_eventTimeInterval = "UIControl_eventTimeInterval";
static const char *UIControl_enventIsIgnoreEvent = "UIControl_enventIsIgnoreEvent";


// runtime 动态绑定 属性
- (void)setIsIgnoreEvent:(BOOL)isIgnoreEvent
{
    objc_setAssociatedObject(self, UIControl_enventIsIgnoreEvent, @(isIgnoreEvent), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (BOOL)isIgnoreEvent{
    return [objc_getAssociatedObject(self, UIControl_enventIsIgnoreEvent) boolValue];
}

- (NSTimeInterval)eventTimeInterval
{
  return [objc_getAssociatedObject(self, UIControl_eventTimeInterval) doubleValue];
}

- (void)setEventTimeInterval:(NSTimeInterval)eventTimeInterval
{
 objc_setAssociatedObject(self, UIControl_eventTimeInterval, @(eventTimeInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

+ (void)load
{
  // Method Swizzling
 static dispatch_once_t onceToken;
 dispatch_once(&onceToken, ^{
       SEL selA = @selector(sendAction:to:forEvent:);
       SEL selB = @selector(_wxd_sendAction:to:forEvent:);
       Method methodA = class_getInstanceMethod(self,selA);
       Method methodB = class_getInstanceMethod(self, selB);

       BOOL isAdd = class_addMethod(self, selA, method_getImplementation(methodB), method_getTypeEncoding(methodB));

       if (isAdd) {
           class_replaceMethod(self, selB, method_getImplementation(methodA), method_getTypeEncoding(methodA));
       }else{
           //添加失败了 说明本类中有methodB的实现,此时只需要将methodA和methodB的IMP互换一下即可。
          method_exchangeImplementations(methodA, methodB);
      }
 });
}

- (void)_wxd_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event
{
  self.eventTimeInterval = self.eventTimeInterval == 0 ? defaultInterval : self.eventTimeInterval;
  if (self.isIgnoreEvent){
      return;
  }else if (self.eventTimeInterval > 0){
      dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.eventTimeInterval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
       [self setIsIgnoreEvent:NO];
       });
  }

  self.isIgnoreEvent = YES;
  // 这里看上去会陷入递归调用死循环,但在运行期此方法是和sendAction:to:forEvent:互换的,相当于执行sendAction:to:forEvent:方法,所以并不会陷入死循环。
  [self _wxd_sendAction:action to:target forEvent:event];
}

Comments