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");
});