美文网首页iOS开发深度好文Swift&Objective-CiOS Developer
按钮、Cell暴力点击触发多次响应的问题研究

按钮、Cell暴力点击触发多次响应的问题研究

作者: 小白进城 | 来源:发表于2017-07-31 17:28 被阅读143次

    在app使用的过程中,由于网络差,手机卡顿等问题,用户在不经意的时候可能多次操作,造成多次请求、或者页面多次push等,给用户带来了不好的体验,那么如何解决相关的问题呢?本文介绍几种解决办法,在某种程度上可以减少这类事件的发生,但是都未能优雅完美的解决根本问题,如果读者有更好的解决办法,欢迎留言讨论。


    一、按钮的暴力点击

    方案一:通过设置enable属性

    -(void)btnAction:(UIButton*)btn{
        btn.enabled = NO;
        // do something
        btn.enabled = YES;
    }
    

    点评:这种方案通过点击按钮时,将按钮的响应点击能力关闭,当完成某中业务(如数据获取)时再开启。

    优点:满足我们最终想要的需求
    缺点:每个按钮都需要这样手动设置,开启时需要格外的注意适当的时机,否则适得其反


    方案二:设置响应点击事件的时间间隔(不推荐)

    -(void)btnAction:(UIButton*)btn{
        [[self class] cancelPreviousPerformRequestsWithTarget:self selector:@selector(doSomeThing) object:btn];
        [self performSelector:@selector(doSomeThing) withObject:btn afterDelay:2.0f];// 时间间隔
    }
    -(void)doSomeThing{
        NSLog(@"click");
    }
    

    点评:这种方案是每次点击时,取消上次的响应事件,并设置为延迟执行,如果用户在延迟执行的时间间隔内一直点击,则点击事件一直被取消,不会执行,虽然可以避免用户的暴力操作,但是第一次点击都不会被执行,不能及时响应,用户体验极差,不推荐,除非业务需要就是如此

    优点:无


    方案三:分类

    该例子实现了UIButton的父类UIControl,通过运行时来实现按钮的间断响应功能

    .h 文件

    @interface UIControl (NonViolentClicked)
    // 接受事件响应的时间间隔,开放出来供外部设置
    @property (nonatomic, assign) NSTimeInterval acceptEventInterval;
    @end
    

    .m 文件

    #import <objc/runtime.h> // 运行时
    @interface UIControl ()
    @property (nonatomic, assign) BOOL ignoreEvent; // 是否忽视点击的事件
    @end
    @implementation UIControl (NonViolentClicked)
    // 使用运行时关联UIControl的点击事件
    +(void)load
    {
        Method a = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
        Method b = class_getInstanceMethod(self, @selector(custom_sendAction:to:forEvent:));
        method_exchangeImplementations(a, b);
    }
    // 自定义的点击事件
    - (void)custom_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event
    {
        if (self.ignoreEvent) return;
        if (self.acceptEventInterval > 0){
            self.ignoreEvent = YES;
            [self performSelector:@selector(setIgnoreEvent:) withObject:@(NO) afterDelay:self.acceptEventInterval];
        }
        [self custom_sendAction:action to:target forEvent:event];
    }
    // 实现acceptEventInterval的getter方法
    -(NSTimeInterval)acceptEventInterval{
        return [objc_getAssociatedObject(self, _cmd) doubleValue]?[objc_getAssociatedObject(self, _cmd) doubleValue]:1.0; // 默认的响应时间为1秒
    }
    // 实现acceptEventInterval的setter方法
    -(void)setAcceptEventInterval:(NSTimeInterval)acceptEventInterval{
        objc_setAssociatedObject(self, @selector(acceptEventInterval), [NSNumber numberWithDouble:acceptEventInterval], OBJC_ASSOCIATION_ASSIGN);
    }
    // 实现ignoreEvent的getter方法
    -(BOOL)ignoreEvent{
        return [objc_getAssociatedObject(self, _cmd) doubleValue];
    }
    // 实现ignoreEvent的setter方法
    -(void)setIgnoreEvent:(BOOL)ignoreEvent{
        objc_setAssociatedObject(self, @selector(ignoreEvent), [NSNumber numberWithBool:ignoreEvent], OBJC_ASSOCIATION_ASSIGN);
    }
    

    使用:

    -(void)btnAction:(UIButton*)btn{
        NSLog(@"click");
    }
    

    点评:上述两种的结合,使用分类并设置点击响应时间间隔

    优点:可以在不改变原有UIButton点击事件的基础上减少暴力点击的发生,一劳永逸;相比方案二,可以及时响应点击事件,并在时间间隔后继续响应

    缺点:需要新建分类文件,代码多;通过设置响应时间间隔并不能完美解决暴力点击的问题,因为有时候网络请求慢,用户可能会在时间间隔外再次点击按钮(这时候则需要我们取消上次网络请求)


    方案四:分类的另外一种设想

    我的另外一种想法是,在点击事件发生时,即改变按钮的enable属性为NO,锁定按钮不可点击,在某个阶段时再将enable设为YES

    则分类文件中,自定义响应事件变为:

    - (void)custom_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event
    {
        self.enabled = NO; // 不响应点击事件
        [self custom_sendAction:action to:target forEvent:event];
    }
    

    再执行完点击业务时再将enable设置为YES;

    点评:这种方案结合了方案一和方案三,但是个人认为这样做还不如方案一来的简洁直观

    优点:只需在合适的时机开启enable;可以手动设置enable,掌握可响应时机
    缺点:不直观

    总结:个人还是推荐方案一和方案三,当然方案一想必是我们一直使用的手段,方案三提供了一种新的解决思路。如果方案四UIControl的分类可以自身获取到开启enable的时机,不失为暴力点击最完美的解决方案了,可以笔者能力有限,如果有读者有更好的解决方案,欢迎赐教。





    二、cell的暴力点击

    本着模仿UIButton的思路来解决cell的暴力点击,但是UITableView和UIBttoun的种种的不同,都未能找到较好的方案,只能粗暴一点的处理了。。。

    暴力方案:使用标志符

    {   // 新增cell点击标记
        BOOL cellSelected;
    }
    
    -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
        if (cellSelected) {
            return;
        }else{
            cellSelected = YES;
            [self performSelector:@selector(didSelectRowAtIndexPath:) withObject:indexPath];
            //[self performSelector:@selector(didSelectRowAtIndexPath:) withObject:indexPath afterDelay:2.0]; // 延迟执行,不推荐
        }
    }
    -(void)didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
        NSLog(@"%ld",indexPath.row);
        // 模拟事件处理,2秒之后再次响应
        // do something
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            cellSelected = NO;
        });
    }
    

    点评:这种方法适用大部分的响应事件,包括按钮点击事件,tableView的点击事件,或者手势等,只是这样处理显得太粗暴,一点都不优雅,但是笔者现在能力有限,实在想不到其他比较好的方式了,555,如果读者有更好的解决方案,不吝赐教啊,感谢!


    三、相关阅读

    1、iOS 分类和扩展

    2、iOS 运行时(runtime)

    3、iOS UIButton点击事件传递参数的解决办法

    相关文章

      网友评论

        本文标题:按钮、Cell暴力点击触发多次响应的问题研究

        本文链接:https://www.haomeiwen.com/subject/ofmrlxtx.html