美文网首页
iOS实战 | 封装最适合你APP的整套loading

iOS实战 | 封装最适合你APP的整套loading

作者: Lol刀妹 | 来源:发表于2019-04-16 21:47 被阅读0次
    iu

    前言

    loading作为APP的基础控件,虽然基本实现思路很简单:在view上放一个半透明view。但是要设计一个简单易用且优雅的loading还是需要思考下的。

    思考?不存在的,对于我这种写了快3年UI的人来说,随手一写就是一个优雅的loading,还需要思考什么?

    审视

    以前写的loading:
    iOS开发造轮子 | Loading图

    当时我的思路是直接把一个自定义view放在delegate.window上,核心方法就两个:show和dismiss。因为view挡住了整个屏幕,因此用户交互也就彻底被阻断了,甚至loading期间用户连返回按钮都点不了,这一点实在不友好,然后我就加了一个类方法,动态设置loading的userInteractionEnabled。这样,用户就可以“点穿”loading触及返回按钮了。

    不得不说当年的我还是比较“有想法”的,但还是稚嫩了点。

    可能是因为我的强迫症越来越严重了,点穿loading这种操作在现在的我看来是不可理喻的。

    注:因为keyWindow是动态变化的,故设计cover类的view时不建议将view放在keyWindow上。详情:iOS开发笔记 | 看完这篇就不会再被keyWindow坑了

    分析

    要设计最适合APP的整套loading首先要将loading的使用情景考虑全面。

    最常见的,跳转到一个页面,请求数据,展示loading,弱网情况下,这个loading可能会转很久,有些用户等不急了可能就会点返回。因此这种情况下决不能将loading放在delegate.window上。

    而某些情况下,我们是真的不想让用户进行任何操作,比如说支付请求中。这个时候就应该把loading放在delegate.window上。

    上面两个例子说明:不要把loading的superView写死。

    因此设计接口的时候必然需要一个参数,这个参数表示loading加在哪个view上。

    此外,有时展示loading还需要附带一句说明信息,如:“上传中...”。

    故,还需要一个参数,表示说明信息。

    基于上述,接口设计如下——

    接口

    #import <UIKit/UIKit.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface CQLoading : UIView
    
    + (void)show;
    + (void)showWithInfo:(NSString *)info;
    + (void)showOnView:(UIView *)superView;
    + (void)showOnView:(UIView *)superView withInfo:(NSString *)info;
    
    + (void)remove;
    + (void)removeFromView:(UIView *)superView;
    
    @end
    
    NS_ASSUME_NONNULL_END
    

    你可能会说,咦,不是必须指定loading添加的那个view吗?那+ (void)show;+ (void)showWithInfo:(NSString *)info;是怎么回事?

    唉,就让我偷下懒嘛:

    #define CQLoadingDefaultView [UIApplication sharedApplication].delegate.window
    
    + (void)show {
        [CQLoading showOnView:CQLoadingDefaultView withInfo:@""];
    }
    

    设置了一个loading添加的默认view,未指定添加view时,loading就放在这个view上。

    这里一共4个show方法,其实都是调用的同一个方法:

    #pragma mark - show
    
    + (void)show {
        [CQLoading showOnView:CQLoadingDefaultView withInfo:@""];
    }
    
    + (void)showWithInfo:(NSString *)info {
        [CQLoading showOnView:CQLoadingDefaultView withInfo:info];
    }
    
    + (void)showOnView:(UIView *)superView {
        [CQLoading showOnView:superView withInfo:@""];
    }
    
    + (void)showOnView:(UIView *)superView withInfo:(NSString *)info {
        // 先将view上的loading移除
        [CQLoading removeFromView:superView];
        
        CQLoading *loading = [[CQLoading alloc] initWithInfo:info];
        [superView addSubview:loading];
        [loading mas_makeConstraints:^(MASConstraintMaker *make) {
            make.left.top.width.height.mas_equalTo(superView);
        }];
    }
    

    remove也一样:

    #pragma mark - remove
    
    + (void)remove {
        [CQLoading removeFromView:CQLoadingDefaultView];
    }
    
    + (void)removeFromView:(UIView *)superView {
        for (UIView *subView in superView.subviews) {
            if ([subView isMemberOfClass:[CQLoading class]]) {
                [subView removeFromSuperview];
            }
        }
    }
    

    这里的设计思路参考的是《Effective OC》的第16条:提供“全能初始化方法”。其实不管参不参考,你不这样写肯定会写很多重复代码,你要干掉那些重复代码最后肯定也会写成这样。

    还有个问题是:是否需要切换到主线程?

    show和remove这些UI操作都是只在主线程才生效的,像loading这种控件基本上都是伴随着网络请求出现的,而网络请求通常意味着异步操作。

    所以,将show和remove的内部实现强制切换到主线程,解决在子线程操作无效的“bug”,让这个loading更加健壮,岂不美哉?

    说得很对是吧?

    我想说,在子线程显示或移除loading,本来就是你的使用方式不对。。。

    在子线程进行UI操作,这是你的问题,不是我的问题。

    自己的问题自己解决。

    并且我还给你暴露出来了,还不赶紧解决。

    我记得SVProgressHUD在子线程操作也是无效的。

    demo

    https://github.com/CaiWanFeng/AlertToastHUD

    最后

    有好的点子不要藏着掖着啊,告诉我啊!

    相关文章

      网友评论

          本文标题:iOS实战 | 封装最适合你APP的整套loading

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