项目中常会遇到在按钮的点击事件中去执行一些耗时操作。如果处理不当经常会出现连续多次点击push多次的情况,造成不好的用户体验。

一种情况是用户快速连续点击,这种情况无法避免。另一种情况是点击一次后响应时间太长,导致用户一直停留在点击界面,也会去再此点击按钮确认是否能执行下一个界面。虽然我们可以在用户点击一次后去显示一个HUB窗口隔绝用户操作,但我们并不清楚服务器去响应这个操作究竟需要多长时间,如果HUB指示器显示时间太长会显得响应特别慢,如果太短,用户很可能在指示器消失后再去点击Button,这时也会出现重复push多次。

通常有三种方式解决此问题。

一、先说一种最不推荐使用的方法。

如果你的Navigation是自定义的,可以重写- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated方法,在此方法中做处理,代码如下:

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
      {
            if (![[super topViewController] isKindOfClass:[viewController class]]) {  // 如果和上一个控制器一样,隔绝此操作
                [super pushViewController:viewController animated:animated];
             }
      }

此中方法可以防止多次重复push,但如果你想push的下一个控制器恰好和上一个控制器类型(Class)一样,就不会push成功。所以并不推荐使用此方法。

二、第二种方式,点击一次后将button的enabled变为NO。

具体思路是:如果在button的点击事件中要做耗时操作,可能是网络请求和请求成功后的数据处理比较耗时。如果只是单纯的在请求成功和失败的回调中写一遍btn.enabled = YES就会发现如果连续点击还是会出现push多次的情况。原因可能是push操作需要时间去执行,我们在这段时间连续快速点击还是会导致push多次,感兴趣的可以去试一下。我的思路是在请求失败后单独将buuton的enabled设为YES,请求成功后不对button做任何操作。最后在- viewWillAppear方法中将button的enabled设为YES,以防在pop回本控制器的时候button不可点击。下面上代码:

其中的WXDHTTPTool是封装了一层AFNetworking的网络请求类。

在- viewWillAppear方法中将button的enabled设为YES:

这样做也可以实现防止重复push的问题,但并不能做到一劳永逸。每个控制器都需要这么去做,虽然代码并不复杂,但方式并不优雅。

三、最优雅的方式,使用Runtime监听点击事件,忽略重复点击,设置一个eventTimeInterval属性,使其规定时间内只响应一次点击事件。废话不多说,上代码。

1、为UIButton创建一个分类,这里我起名为WXD。

2、.h文件:添加一个属性eventTimeInterval,用来设置button点击间隔时间。

#import <UIKit/UIKit.h>
    @interface UIButton (WXD)
     /**
     *  为按钮添加点击间隔 eventTimeInterval秒
     */
    @property (nonatomic, assign) NSTimeInterval eventTimeInterval;
    @end

3、.m文件:需要import<objc/runtime.h>库。

#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];
  }

最后就可以去在想要设置点击间隔的控制器引入分类的头文件,可以手动设置button.eventTimeInterval = 点击间隔,也可以不设任何值去使用默认的时间间隔。还可以在pch文件中引入分类头文件,让项目中所有button都添加此分类。

写在最后:如果有意见或者更优雅的解决方式,欢迎沟通交流。

iOS防止button重复点击的更多相关文章

  1. iOS修改button的点击范围

    一般来说,按钮的点击范围是跟按钮的大小一样的.若按钮很小时,想增大点击区域,网上通用的方法有①设置btn图片setImage,然后将btn的size设置的比图片大②在btn上添加一个比较大的透明btn ...

  2. 回车键和button按钮都绑定同一个事件,如何避免按回车的时候button重复点击

    保存一个全局变量,用来记录Button的焦点状态 <button onclick="login();" onfocus="window.buttonIsFocuse ...

  3. iOS 防止UIButton重复点击

    使用UIButton的enabled或userInteractionEnabled 使用UIButton的enabled属性, 在点击后, 禁止UIButton的交互, 直到完成指定任务之后再将其en ...

  4. iOS之防止用户重复点击Button(按钮)问题

    在项目中,我们往往会遇到这样的问题:因为网络较慢的原因,用户会不耐烦的一直去点击按钮,这样导致的结果时:相关代码一遍一遍的被重复执行,如果按钮的事件是网络请求的话,这样又导致一种网络请求的循环.所以我 ...

  5. 限制 button 在 3 秒内不可重复点击

    在下载或者上传文件过程中避免重复点击带来的多次同样的请求造成资源浪费,限制 button 的点击次数是很有必要的. 1. 增强用户体验,2. 减轻服务器压力. HTML 代码 <button i ...

  6. iOS小技巧:用runtime 解决UIButton 重复点击问题

    http://www.cocoachina.com/ios/20150911/13260.html 作者:uxyheaven 授权本站转载. 什么是这个问题 我们的按钮是点击一次响应一次, 即使频繁的 ...

  7. iOS不得姐项目--TabBar的重复点击实现当前模块刷新;状态栏点击实现当前模块回滚到最顶部

    一.实现功能:重复点击tabBar,刷新当前TableView,其余不受影响 <1>实现思路: 错误的方法: TabBar成为自己的代理,监听自己的点击--这种方法是不可取的,如果外面设置 ...

  8. ios 添加到cell 上的button点击无效!扩大button的点击区域(黑魔法)

    一般情况下点击效果都是正常的!要不然你对它做了什么?一般细心的小伙伴都没有遇到这种情况,但是呢! 当然我是二班的!在这里我主要讲两个问题,解决问题和普及魔法. 一.普及问题(button在cell上点 ...

  9. iOS小技巧–用runtime 解决UIButton 重复点击问题

    什么是这个问题 我们的按钮是点击一次响应一次, 即使频繁的点击也不会出问题, 可是某些场景下还偏偏就是会出问题. 通常是如何解决 我们通常会在按钮点击的时候设置这个按钮不可点击. 等待0.xS的延时后 ...

随机推荐

  1. Xcode 9安装

    Xcode 9 Xcode 9 Compatibility Xcode 9 requires a Mac running macOS 10.13.2 or later. Xcode 9 include ...

  2. shell中$*与$@的区别

    $*所有的位置参数,被作为一个单词 注意:"$*"必须被""引用 $@ 与$*同义,但是每个参数都是一个独立的""引用字串,这就意味着参数被 ...

  3. 64_s1

    SAASound-3.2-17.fc26.i686.rpm 13-Feb-2017 22:13 27650 SAASound-3.2-17.fc26.x86_64.rpm 13-Feb-2017 23 ...

  4. Deep Learning基础--SVD奇异值分解

    矩阵奇异值的物理意义是什么?如何更好地理解奇异值分解?下面我们用图片的例子来扼要分析. 矩阵的奇异值是一个数学意义上的概念,一般是由奇异值分解(Singular Value Decomposition ...

  5. centos 6.5配置ftp服务器,亲测可用

    设置开机启动 1 chkconfig vsftpd on 启动服务 1 /sbin/service vsftpd start 配置FTP用户组/用户以及相应权限 添加用户组 1 groupadd ft ...

  6. Python学习笔记——数据结构和算法(一)

    1.解压序列赋值给多个变量 任何的序列(或者是可迭代对象)可以通过一个简单的赋值语句解压并赋值给多个变量. 唯一的前提就是变量的数量必须跟序列元素的数量是一样的. >>> data ...

  7. PHP利用rand(1,100)函数产生10个1~100之间的随机数

    //echo rand(1,100); $max=0; $min=100; for($i=0;$i<=9;$i++){ $rand[$i]=rand(1,100); if($rand[$i]&g ...

  8. 7. Docker Compose 项目

  9. android4.0 锁屏实现(转)

    转载请表明出处:http://blog.csdn.net/wdaming1986/article/details/8837023 好了,言归正传,说说锁屏了,其实把锁屏做成apk的形式,会引起很多问题 ...

  10. c++ primer 6 语句

    没什么重要的东西,异常处理在17章再讲吧