iOS 加载等待动画

作者: 梦回蓝桥 | 来源:发表于2018-03-02 17:51 被阅读137次

    先来看看实现的效果图

    donghuagif2.gif

    类似于猫眼电影加载等待动画的效果(盯着猫眼好好看,有惊喜!)

    maoyangif.gif

    这是我在之前公司项目中使用的加载等待动画,现项目中使用的还是系统的UIActivityIndicatorView,等后期项目优化会让UI做些图片更换,说多了,来说说这种类似gif动画的实现方式吧.

    一种是使用系统的动画类(基于CAAnimation)来实现视图的旋转效果.
    另一种是使用UIImageView加载动态视图(一系列帧视图)来实现.

    本文就是使用的第二种.

    关于实现:无非就是在window或者当前VC中加载一个UIView,在此UIView上加载UIImageView+UILabel,设置UIImageView.animationImages 加载需要播放的帧视图数组,调用[self.imageView startAnimating];实现动画的播放!

    这里我创建了两个视图,一个是底部的view(SXLoadingView),另一个是加在SXLoadingView上面的动画播放的视图(SXAnimationIndicator).

    其中SXLoadingView.h

    #import <UIKit/UIKit.h>
    #import "SXAnimationIndicator.h"
    @interface SXLoadingView : UIView
    
    @property (nonatomic, weak) SXAnimationIndicator *animationIndicator;
    
    
    +(SXLoadingView *)loadingView;
    
    /**
     *  加载并开始动画,默认将视图SXLoadingView加载在window上
     */
    + (SXLoadingView *)loadingViewAndStartAnimation;
    
    
    /**
     *
     *  加载并开始动画,默认将SXLoadingView视图加载在window上
     *
     *  @param frame  frame
     *
     *  @param title  显示的标题(加载中...)
     */
    + (SXLoadingView *)loadingViewAndStartAnimationWithFrame:(CGRect)frame title:(NSString *)title;
    
    
    /**
     *
     *  @param frame  frame
     *
     *  @param title  显示的标题(加载中...)
     *
     *  @param view   loadView父视图
     *
     */
    + (SXLoadingView *)loadingViewAndStartAnimationWithFrame:(CGRect)frame title:(NSString *)title InView:(UIView *)view;
    
    
    @end
    

    SXLoadingView.m

    #import "SXLoadingView.h"
    #import "UIView+SX.h"
    
    @implementation SXLoadingView
    
    #define ScreenSize ([UIScreen mainScreen].bounds.size)
    /** 设备为iphonex */
    #define DEVICE_IS_iPhoneX ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1125, 2436), [[UIScreen mainScreen] currentMode].size) : NO)
    /** 距屏幕上边距离 */
    #define TOP_HEIGHT (DEVICE_IS_iPhoneX ? 88 : 64)
    /** 距屏幕下边距离 */
    #define BOTTOM_HEIGHT (DEVICE_IS_iPhoneX ? 34 : 0)
    
    + (SXLoadingView *) loadingView
    {
        return [[self alloc]init];
    }
    
    - (instancetype) init
    {
        if (self = [super init]) {
            self.alpha = 1;
            SXAnimationIndicator *indicator = [[SXAnimationIndicator alloc]initWithFrame:CGRectMake(0, (ScreenSize.height - TOP_HEIGHT - 80)/2, ScreenSize.width, 50+10+20)];
            indicator.imageView.centerX = ScreenSize.width/2;
            indicator.label.centerX = ScreenSize.width/2;
            [self addSubview:indicator];
            self.animationIndicator = indicator;
        }
        return self;
    }
    
    /**
     *  加载并开始动画,默认将视图SXLoadingView加载在window上
     */
    + (SXLoadingView *)loadingViewAndStartAnimation
    {
        SXLoadingView *loadView = [SXLoadingView loadingView];
        loadView.frame = CGRectMake(0, TOP_HEIGHT,ScreenSize.width, ScreenSize.height);
        UIWindow *window = [[[UIApplication sharedApplication] delegate] window];
        loadView.layer.zPosition = INT8_MAX;
        [window addSubview:loadView];
        [loadView.animationIndicator startAnimation];
        return loadView;
    }
    
    /**
     *
     *  加载并开始动画,默认将SXLoadingView视图加载在window上
     *
     *  @param frame  frame
     *
     *  @param title  显示的标题(加载中...)
     */
    + (SXLoadingView *)loadingViewAndStartAnimationWithFrame:(CGRect)frame title:(NSString *)title
    {
        SXLoadingView *loadView = [SXLoadingView loadingView];
        if (CGRectIsNull(frame)) {
            frame = CGRectMake(0, TOP_HEIGHT,ScreenSize.width, ScreenSize.height);
        }
        [loadView showTitle:title];
        loadView.frame = frame;
        //使用keyWindow当你的app需要跳转到别的app然后返回本app的时候, 有可能会导致UI错乱
        //UIWindow* window = [UIApplication sharedApplication].keyWindow;
        UIWindow *window = [[[UIApplication sharedApplication] delegate] window];
    
        // 解决了loadview被VC中view覆盖没法显示的问题(查看视图层级还是被覆盖了)
        loadView.layer.zPosition = INT8_MAX;
        [window addSubview:loadView];
        [loadView.animationIndicator startAnimation];
        return loadView;
    }
    
    
    /**
     *
     *  @param frame  frame
     *
     *  @param title  显示的标题(加载中...)
     *
     *  @param view   loadView父视图
     *
     */
    + (SXLoadingView *)loadingViewAndStartAnimationWithFrame:(CGRect)frame title:(NSString *)title InView:(UIView *)view
    {
        SXLoadingView *loadView = [SXLoadingView loadingView];
        [loadView showTitle:title];
        if (CGRectIsNull(frame)) {
            frame = CGRectMake(0, TOP_HEIGHT,ScreenSize.width, ScreenSize.height);
        }
        loadView.frame = frame;
        [view addSubview:loadView];
        [loadView.animationIndicator startAnimation];
        return loadView;
    }
    
    // 此接口可开放出去
    - (SXAnimationIndicator *)showTitle:(NSString *)title
    {
        self.animationIndicator.label.text = title;
        return self.animationIndicator;
    }
    
    @end
    

    SXAnimationIndicator.h

    #import <UIKit/UIKit.h>
    
    @interface SXAnimationIndicator : UIView
    
    
    @property (nonatomic, weak)   UIImageView *imageView;
    @property (nonatomic, strong) UILabel *label;
    @property (nonatomic, assign) BOOL isAnimating;
    
    - (id) initWithFrame:(CGRect)frame;
    
    - (void) startAnimation;
    
    - (void) stopAnimation;
    
    
    @end
    

    SXAnimationIndicator.m

    #import "SXAnimationIndicator.h"
    
    @implementation SXAnimationIndicator
    #define ScreenSize ([UIScreen mainScreen].bounds.size)
    
    - (id) initWithFrame:(CGRect)frame
    {
        self = [super initWithFrame:frame];
        if (self) {
            _isAnimating = NO;
            UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 50, 50)];
            [self addSubview:imageView];
            _imageView = imageView;
            _imageView.animationImages = [self getImageArray];
            _label = [[UILabel alloc]initWithFrame:CGRectMake(0, CGRectGetMaxY(_imageView.frame)+10, ScreenSize.width, 20)];
            _label.textAlignment = NSTextAlignmentCenter;
            _label.textColor = [UIColor lightGrayColor];
            _label.font = [UIFont systemFontOfSize:13];
            [self addSubview:_label];
            self.layer.hidden = YES;
        }
        return self;
    }
    
    - (NSArray *)getImageArray {
    
        NSMutableArray *imageArray = [[NSMutableArray alloc] init];
        for (int i = 1; i <= 8; i++) {
        NSString *imageName = [NSString stringWithFormat:@"animation%d.png", i];
    
        // 性能优化,直接以文件路径的形式创建image,没有缓存,自动释放,imageNamed: 有缓存退出程序释放
        NSString *path = [[NSBundle mainBundle] pathForResource:@"animationImage" ofType:@"bundle"];
        NSString *imagePath = [NSString stringWithFormat:@"%@/%@", path, imageName];
        
        UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
            
        [imageArray addObject:image];
        }
        return imageArray;
    }
    
    - (void)startAnimation
    {
        _isAnimating = YES;
        self.layer.hidden = NO;
        [self doAnimation];
    }
    
    -(void)doAnimation{
    
        //设置动画总时间
        self.imageView.animationDuration = 1;
        //设置重复次数,0表示无限
        self.imageView.animationRepeatCount = 0;
        //开始动画
        [self.imageView startAnimating];
    }
    
    - (void) stopAnimation
    {
        _isAnimating = NO;
        [self.imageView stopAnimating];
        self.imageView.animationImages = nil;
    }
    
    
    @end
    

    使用样例 直接在你需要展示的VC中

    - (void)viewDidLoad {
    
        [super viewDidLoad];
        
        /* 将各参数分开
        SXLoadingView* loadingView = [SXLoadingView loadingView];
        [self.view addSubview:loadingView];
        loadingView.frame = CGRectMake(0, TOP_HEIGHT, ScreenSize.width, ScreenSize.height);
        loadingView.animationIndicator.label.text = @"加载中...";
        [loadingView.animationIndicator startAnimation];
        self.loadingView = loadingView;
        */
        // 将参数封装在一起
        self.loadingView = [SXLoadingView loadingViewAndStartAnimationWithFrame:CGRectMake(0, TOP_HEIGHT, ScreenSize.width, ScreenSize.height) title:@"加载中..." InView:self.view];
    
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [self.loadingView.animationIndicator stopAnimation];
            [self.loadingView removeFromSuperview];
        });
    }
    

    最后来说说实现过程中遇到以及需要注意的问题.

    1. 关于加载帧动画图片性能优化的处理

    使用的是imageWithContentsOfFile 来加载图片,而非imageNamed:
    前者还需要将图片放入.bundle文件中,是否过于麻烦?
    关于这个问题不做过多讲解了,参考下面这篇文章
    http://blog.csdn.net/zyr124/article/details/50053185

    2. 关于内存泄漏的处理

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            // 以前只是简单的将视图移除了
            [self.loadingView removeFromSuperview];
        });
    


    修改后

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self.loadingView.animationIndicator stopAnimation];
        [self.loadingView removeFromSuperview];
    });
    

    3. window为nil

    //window为nil
    UIWindow *window = [UIApplication sharedApplication].keyWindow;
    


    更改后

    UIWindow *window = [[[UIApplication sharedApplication] delegate] window];
    


    此时window是不为nil了,但是加在window上面的loadView,被VC视图遮挡(紫色部分)

    屏幕快照 2018-03-02 下午4.52.43.png

    于是我想到了使用 bringSubviewToFront 来实现,可是不起作用.

    直到做了下面的处理之后

     /* 
      * 改变图层的显示顺序(但不能改变的事件的传递顺序,所以视图层级还是原来那样,该覆盖的还是覆盖)给zPosition提高一个像素 (视图都非常薄,0.1也能做到)
      * 和UIView严格的二维坐标系不同,CALayer存在于一个三维空间当中。
      * zPosition最实用的功能就是改变图层的显示顺序了
      */
    loadView.layer.zPosition = INT8_MAX;//你传递 1 也可以
    


    运行效果正常

    weizhigif.gif

    可是打开视图层级,发现层级跟上面的还是一样是被覆盖的(虽然运行效果正常!!!)


    屏幕快照 2018-03-02 下午5.10.16.png

    这是因为使用 loadView.layer.zPosition = INT8_MAX;只是改变了视图层级的“显示顺序”,事件的传递以及层级还是跟原来一样,并未改变.

    相关文章

      网友评论

        本文标题:iOS 加载等待动画

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