项目中常会遇到在按钮的点击事件中去执行一些耗时操作。如果处理不当经常会出现连续多次点击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. wifi钓鱼 强势拿你的wifi密码

    钓鱼wifi 首先设一个场景!!! 如何得到一个免费的wifi  有人可能做过抓包跑包的方法或者跑pin码的方法然而这些方法可能会耗去你大量的时间(我曾经跑包花了一天的时间  跑pin码花了一晚上)感 ...

  2. go标识符、变量、常量

    标识符 标识符是用来表示Go中的变量名或者函数名,以字母或_开头.后可跟着字母.数字. _ 关键字 关键字是Go语言预先定义好的,有特殊含义的标识符. 变量 1. 语法:var identifier ...

  3. java线上应用故障排查之二:高内存占用【转】

    前一篇介绍了线上应用故障排查之一:高CPU占用,这篇主要分析高内存占用故障的排查. 搞Java开发的,经常会碰到下面两种异常: 1.java.lang.OutOfMemoryError: PermGe ...

  4. [node.js] async/await如何优雅处理异常?

    node.js的世界,从callback开始,不会止于async. 所有人都在骂为什么不能完全进化,其实我感觉这就是老外的细心,为了承上.这也就是为什么async其实就是promise一样,假如不是一 ...

  5. 使AJAX可缓存——基于flask

    主流浏览器都有缓存机制,主要基于HTTP协议定义的缓存策略.对于一定时间内不发生变动的文档缓存起来,对于下次请求,就可以直接返回缓存的结果.使用缓存有以下好处: 1.减少冗余的数据传输,节省网络流量成 ...

  6. python 随机字符串

    pip3 install pillow 读取硬盘中的文件,在页面显示 f = open('static/imgs/yj.png','rb') data = f.read() f.close() ret ...

  7. 构建最基础的Spring项目及所需要的jar包

    1.Spring 框架由6个模块组成 上图是spring4的结构图,不同版本结构略有差异,但模块都是相同的. 2.Core Container 核心容器 容器是Spring的核心部分,Core Con ...

  8. 机顶盒 gettimeofday()获取毫秒溢出

    最近在写代码的时候遇见了一个bug,在获取当前时间戳的毫秒时,我自己测试的时候总是OK的,但是测试那边总是测不对,之前一直以为是因为我存储的类型的不对,从long long类型从lld改成llu,然后 ...

  9. IndiaHacks 2016 - Online Edition (Div. 1 + Div. 2) E - Bear and Forgotten Tree 2 链表

    E - Bear and Forgotten Tree 2 思路:先不考虑1这个点,求有多少个连通块,每个连通块里有多少个点能和1连,这样就能确定1的度数的上下界. 求连通块用链表维护. #inclu ...

  10. 基于jquery扩展漂亮的CheckBox

    大家都知道默认的html复选框控件样式可定义相当有限,无法满足大多用户的美观度.今天跟大家一起分享前一段时间自己编写的CheckBox控件.喜欢的朋友可以拿去使用,有什么好的建议希望你给我留言.废话不 ...