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

iu

前言

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

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

iOS实战 | 封装最适合你APP的整套loading_第1张图片

审视

以前写的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 

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_第2张图片

最后

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

你可能感兴趣的:(iOS实战 | 封装最适合你APP的整套loading)