您的位置:首页 > 移动开发 > IOS开发

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

2017-07-20 10:02 681 查看
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:
方法,注意
selector
object
参数需要一一对应。如下代码:

- (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];

   

}

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



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

使用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的实现,此时只需要将methodA和methodB的IMP互换一下即可。

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

            
        }

        
    }

    

    //此处methodA和methodB方法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");

            });
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: