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都添加此分類。

寫在最后:如果有意見或者更優雅的解決方式,歡迎溝通交流。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM