iOS之a按钮重复点击的问题、cell重复点击的问题

时间:2024-05-21 07:43:20

1.=====网络请求时,按钮重复点击的问题==

存在网络请求的按钮事件, 这种事件应该在开发中是经常遇到的, 
例如:登录按钮事件,如果我们快速多次点击按钮, 会不会多次触发登录请求?第一次登录请求结果未返回之前,再次点击登录按钮, 要不要触发下次登录请求?

这种情况下,解决方案其实很简单:那就是在按钮点击之后, 将按钮设置为不可用, 等到网络请求的结果返回后, 重新设置按钮为可用状态。

这里的实现方案很简单, 其实有个小细节就是:我们是使用enabled属性还是userInteractionEnabled属性来设置可用状态, 对于button而言, 如果使用enabled属性, 会发现button的样式发生了变化, 而userInteractionEnabled属性则不会产生任何变化, 建议uiview子类使用userInteractionEnabled来设置可用状态, 而像buttonItem这种就可以使用enabled来设置。

NSObject中的performSelector:withObject:afterDelay:方法将会在当前线程的Run Loop中根据afterDelay参数创建一个Timer,如果没有调用有inModes参数的方法,该Timer会运行在当前Run Loop的默认模式中,也就是NSDefaultRunLoopMode定义的模式中。

performSelector:withObject:afterDelay:方法的使用看起来还是很简单的。这里讲另外一个辅助函数,NSObject中静态的cancelPreviousPerformRequestsWithTarget方法。该方法就是专门用来取消取消performSelector:withObject:afterDelay:方法所创建的Selector source(内部上就是一个Run Loop的Timer source)。因此该方法和performSelector:withObject:afterDelay:方法一样,只限于当前Run Loop中。

 

我们可以利用cancelPreviousPerformRequestsWithTarget直接取消一个对象在当前Run Loop中的所有未执行的performSelector:withObject:afterDelay:方法所产生的Selector Sources,如下代码:

- (void)viewDidLoad
{
    [super viewDidLoad];

    
    [self performSelector:@selector(test:) withObject:nil afterDelay:1];
    [self performSelector:@selector(test:) withObject:@"mgen" afterDelay:2];
    [NSObject cancelPreviousPerformRequestsWithTarget:self];    
}

- (void)test:(id)obj
{
    NSLog(@"调用成功: %@", obj);
}

不会有任何输出,因为两个调用都被取消了。

 

如果想取消单独一个的话,需使用cancelPreviousPerformRequestsWithTarget:selector:object:方法,注意selectorobject参数需要一一对应。如下代码:

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    [self performSelector:@selector(test:) withObject:[NSNumber numberWithInt:26] afterDelay:1];
    [self performSelector:@selector(test:) withObject:[NSNumber numberWithInt:17] afterDelay:2];
    [self performSelector:@selector(test:) withObject:[NSNumber numberWithInt:17] afterDelay:3];
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(test:) object:[NSNumber numberWithInt:17]];    
}

- (void)test:(id)obj
{
    NSLog(@"调用成功: %@", obj);
}

原理是:我们每次点击按钮时,先执行取消之前的按钮点击执行事件,然后再去执行一个延迟执行方法(方法中执行的是按钮执行的事件)

//在将要push出去的时候把导航栏清空,---防止到下一个页面时导航上乱

-(void)viewWillDisappear:(BOOL)animated{

    NSLog(@"View将要消失");

    for (UIView *vinself.navigationController.navigationBar.subviews){

        [v removeFromSuperview];

    }

}



===========方式一  ===

-(void)buttonClicked:(UIButton *)btn{

    NSLog(@"tag0001--%tu",btn.tag);

     btn.enabled=NO;//先把按钮禁用  

[self performSelector:@selector(changeButtonStatus:) withObject:btn afterDelay:2];///2秒后按钮可用---这个可以不执行

    

    //执行我们需要实现的内容

    NSInteger tag=btn.tag-15000;

    if(tag==0){

       

    }elseif(tag==1){

       

        

    }elseif(tag==2){

        


    }


}

    

-(void)changeButtonStatus:(UIButton *)btn{

    NSLog(@"tag0002--%tu",btn.tag);

 btn.enabled =YES;

}



//按钮点击方法

-(void)titleviewBtn:(UIButton *)btn{

NSLog(@"tag000 --%tu",btn.tag);


   

 //先将未到时间执行前的任务取消。取消buttonClicked的点击方法,----和下面的方法参数必须一致,否则不生效

    [[self class]cancelPreviousPerformRequestsWithTarget:selfselector:@selector(buttonClicked:)object:btn];

    //延迟执行方法,

    [selfperformSelector:@selector(buttonClicked:)withObject:btnafterDelay:2];

   

}




==========方式二:

iOS之a按钮重复点击的问题、cell重复点击的问题

=============方法三:

使用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都添加此分类。


*********

#import


#define defaultInterval.5//默认时间间隔


@interfaceUIControl (UIControl_buttonCon)


@property(nonatomic,assign)NSTimeIntervaltimeInterval;//用这个给重复点击加间隔


@property(nonatomic,assign)BOOLisIgnoreEvent;//YES不允许点击NO允许点击


@end


#import"UIControl+UIControl_buttonCon.h"


@implementationUIControl (UIControl_buttonCon)



- (NSTimeInterval)timeInterval


{

    

    return[objc_getAssociatedObject(self,_cmd)doubleValue];

    

}


- (void)setTimeInterval:(NSTimeInterval)timeInterval


{

    

    objc_setAssociatedObject(self,@selector(timeInterval),@(timeInterval),OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    

}


//runtime动态绑定属性


- (void)setIsIgnoreEvent:(BOOL)isIgnoreEvent{

    

    objc_setAssociatedObject(self,@selector(isIgnoreEvent),@(isIgnoreEvent),OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    

}


- (BOOL)isIgnoreEvent{

    

    return[objc_getAssociatedObject(self,_cmd)boolValue];

    

}


- (void)resetState{

    

    [selfsetIsIgnoreEvent:NO];

    

}


+ (void)load{

    

   staticdispatch_once_t onceToken;

    

   dispatch_once(&onceToken, ^{

        

       SEL selA =@selector(sendAction:to:forEvent:);

        

       SEL selB =@selector(mySendAction:to:forEvent:);

        

        Method methodA =class_getInstanceMethod(self, selA);

        

        Method methodB =class_getInstanceMethod(self, selB);

        

        //methodB的实现添加到系统方法中也就是说将methodA方法指针添加成方法methodB的返回值表示是否添加成功

        

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

        

        //添加成功了说明本类中不存在methodB所以此时必须将方法b的实现指针换成方法A的,否则b方法将没有实现。

        

       if(isAdd) {

            

            class_replaceMethod(self, selB,method_getImplementation(methodA),method_getTypeEncoding(methodA));

            

        }else{

            

            //添加失败了说明本类中有methodB的实现,此时只需要将methodAmethodBIMP互换一下即可。

            

           method_exchangeImplementations(methodA, methodB);

            

        }

        

    });

    

}


- (void)mySendAction:(SEL)action to:(id)target forEvent:(UIEvent*)event


{

    

    if([NSStringFromClass(self.class)isEqualToString:@"UIButton"]) {

        

        self.timeInterval=self.timeInterval==0?defaultInterval:self.timeInterval;

        

        if(self.isIgnoreEvent){

            

           return;

            

        }elseif(self.timeInterval>0){

            

            [selfperformSelector:@selector(resetState)withObject:nilafterDelay:self.timeInterval];

            

        }

        

    }

    

    //此处methodAmethodB方法IMP互换了,实际上执行sendAction;所以不会死循环

    

    self.isIgnoreEvent=YES;

    

    [selfmySendAction:actionto:targetforEvent:event];

    

}


@end

================cell重复点击的问题解决:

//防止重复点击

if (self.isSelect == false) {

    self.isSelect = true;

     //在延时方法中将isSelect更改为false

    [self performSelector:@selector(repeatDelay) withObject:nil afterDelay:0.5f];

   // 在下面实现点击cell需要实现的逻辑就可以了

}

- (void)repeatDelay{

      self.isSelect = false;

}


===========cell防止重复点击==

//防连续点击

            UITableViewCell *cell=[tableView cellForRowAtIndexPath:indexPath];

            cell.userInteractionEnabled=NO;

            NSLog(@"cellNO");

            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

                cell.userInteractionEnabled=YES;

                NSLog(@"cellYES");

            });