zoukankan      html  css  js  c++  java
  • iOS防止button重复点击

          项目中常会遇到在按钮的点击事件中去执行一些耗时操作。如果处理不当经常会出现连续多次点击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都添加此分类。

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

  • 相关阅读:
    js 字符串indexOf方法封装
    js 冒泡排序
    CSS定位 position的三个属性 elative 、absolute、fixed :
    让父元素能感知浮动的子元素 #用伪元素清除浮动
    三个路由器的连接,中间路由的配置(静态路由)
    IDEA 添加tomcat出错: Error: Environment variable name is not set 我的解决方法
    通过基于AspectJ 注解的方式实现Spring AOP报 can't find referenced pointcut myPointCut 错误,我的解决方法
    C语言fopen函数打开文本文件与二进制文件的区别
    位运算的奇技淫巧 系列1
    位运算例子(以后会逐渐补充)
  • 原文地址:https://www.cnblogs.com/wanxudong/p/5984941.html
Copyright © 2011-2022 走看看