提示图显示篇之MBProgressHUD(三)

版本记录

版本号 时间
V1.0 2017.05.28

前言

在我们的app项目中,为了增加和用户很好的交互能力,通常都需要加一些提示图,比如说,当我们需要网络加载数据的时候,首先要监测网络,如果网络断开的时候,我们需要提示用户;还有一个场景就是登陆的时候,需要提示用户正在登录中和登录成功;再比如清除用户的缓存数据成功的时候,也需要进行清除成功的提示的,等等。总之,用的场景很多,好的提示图可以增强和用户的交互体验,试想,如果没有网络,也不提示用户,用户还以为还在登录,过了一会还是上不去,那可能用户就疯掉了,怒删app了。最近做的几个项目中也是对这个要求的也很多,在实际应用中可以自己写,也可以使用第三方框架,比较知名的比如MBProgressHUDSVProgressHUD,从这一篇开始我就一点一点的介绍它们以及它们的使用方法,希望对大家有所帮助,那我们就开始喽。先给出github地址:
MBProgressHUD github
感兴趣可以先看上一篇
1.提示图显示篇之MBProgressHUD(一)
2.提示图显示篇之MBProgressHUD(二)

这一篇将对MBProgreeHUD的初始化和动画效果等进行介绍。

详情

一、初始化方法

任何对象都需要进行初始化,MBProgressHUD也不例外,初始化才能在内存中分配空间,才会存取值和运行,代码才会有硬件依托。下面我们先看一下MBProgressHUD的初始化方法。

1. 第一种初始化方法
- (id)initWithWindow:(UIWindow *)window __attribute__((deprecated("Use initWithView: instead.")));

这种初始化方法已经被废弃了,Window修改为View。

2. 这个才是常用的初始化方法
/**
 * A convenience constructor that initializes the HUD with the view's bounds. Calls the designated constructor with
 * view.bounds as the parameter.
 *
 * @param view The view instance that will provide the bounds for the HUD. Should be the same instance as
 * the HUD's superview (i.e., the view that the HUD will be added to).
 */

- (instancetype)initWithView:(UIView *)view;

下面我们就以一个小的demo来说明下MBProgressHUD的初始化方法。

    UIImageView *imageView = [[UIImageView alloc] init];
    imageView.image = [UIImage imageNamed:@"global"];

    MBProgressHUD *HUD = [[MBProgressHUD alloc] initWithView:self.view];
    HUD.mode = MBProgressHUDModeCustomView;
    // 显示隐藏时的动画模式
    HUD.animationType = MBProgressHUDAnimationFade;
    // 关闭绘制的"性能开关",如果alpha不为1,最好将opaque设为NO,让绘图系统优化性能
    HUD.opaque = NO;
    HUD.customView = imageView;
    HUD.label.text = @"全球化";
    HUD.detailsLabel.text = @"我们一起参与";
    [self.view addSubview:HUD];
    [HUD showAnimated:YES];
    [HUD hideAnimated:YES afterDelay:10.0];
初始化图案

上面的就是利用MBProgressHUD *HUD = [[MBProgressHUD alloc] initWithView:self.view]方法来实现的。

如果有特殊的需求需要封装的话,可以自定义一个类继承自MBProgressHUD,然后采用UIView的初始化方法进行初始化和配置,也就是用下面的方法。

- (instanceType)initWithFrame:(CGRect)frame;

二、动画效果

看MBProgressHUD.h文件前几个就是定义几个枚举,有个我们前面已经说了,那就是指示图的样式,下面我们看另外一个枚举,那就是动画效果的枚举定义,如下所示:

typedef NS_ENUM(NSInteger, MBProgressHUDAnimation) {
    // 默认效果,只有透明度变化的动画效果
    MBProgressHUDAnimationFade,
    // 透明度变化+形变效果,其中MBProgressHUDAnimationZoom和
    // MBProgressHUDAnimationZoomOut的枚举值都为1
    MBProgressHUDAnimationZoom,
    //拉远镜头, 先把形变放大到1.5倍,再恢复原状,产生缩小效果
    MBProgressHUDAnimationZoomOut = MBProgressHUDAnimationZoom,
    //拉近镜头, 先把形变缩小到0.5倍,再恢复到原状,产生放大效果
    MBProgressHUDAnimationZoomIn
};

这里默认效果就是MBProgressHUDAnimationFade。

动画显示效果主要是在下面两个方法中实现的。

  • 1.HUD的显示
//HUD的显示
- (void)showUsingAnimation:(BOOL)animated 
{
    // Cancel any scheduled hideDelayed: calls
    [NSObject cancelPreviousPerformRequestsWithTarget:self];
    [self setNeedsDisplay];

    // ZoomIn,ZoomOut分别理解为`拉近镜头`,`拉远镜头`
    // 因此MBProgressHUDAnimationZoomIn先把形变缩小到0.5倍,再恢复到原状,产生放大效果
    // 反之MBProgressHUDAnimationZoomOut先把形变放大到1.5倍,再恢复原状,产生缩小效果
    // 要注意的是,形变的是整个`MBProgressHUD`,而不是中间可视部分
    if (animated && animationType == MBProgressHUDAnimationZoomIn) {
    // 在初始化方法中, 已经定义了rotationTransform = CGAffineTransformIdentity.
    // CGAffineTransformIdentity也就是对view不进行变形,对view进行仿射变化总是原样

    // CGAffineTransformConcat是两个矩阵相乘,与之等价的设置方式是:
    // self.transform = CGAffineTransformScale(rotationTransform, 0.5f, 0.5f);
        self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(0.5f, 0.5f));
    } else if (animated && animationType == MBProgressHUDAnimationZoomOut) {
    // self.transform = CGAffineTransformScale(rotationTransform, 1.5f, 1.5f);
        self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(1.5f, 1.5f));
    }

    self.showStarted = [NSDate date];

    // 开始做动画
    if (animated) {
    // 在初始化方法或者`hideUsingAnimation:`方法中,alpha被设置为0.f,在该方法中完成0.f~1.f的动画效果
        [UIView beginAnimations:nil context:NULL];
        [UIView setAnimationDuration:0.30];
        self.alpha = 1.0f;
    // 从形变状态回到初始状态    
        if (animationType == MBProgressHUDAnimationZoomIn || animationType == MBProgressHUDAnimationZoomOut) {
            self.transform = rotationTransform;
        }
        [UIView commitAnimations];
    }
    else {
        self.alpha = 1.0f;
    }
}
  • 2.HUD的隐藏
// HUD的隐藏
- (void)hideUsingAnimation:(BOOL)animated 
{
    // Fade out
    if (animated && showStarted) {
        [UIView beginAnimations:nil context:NULL];
        [UIView setAnimationDuration:0.30];
        [UIView setAnimationDelegate:self];
        [UIView setAnimationDidStopSelector:@selector(animationFinished:finished:context:)];
          // 当alpha小于0.01时,就会被当做全透明对待,全透明是接收不了触摸事件的.
          // 所以设置0.02防止hud在还没结束动画并调用done方法之前传递触摸事件.
          // 在完成的回调animationFinished:finished:context:才设为0
        if (animationType == MBProgressHUDAnimationZoomIn) {
            self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(1.5f, 1.5f));
        } else if (animationType == MBProgressHUDAnimationZoomOut) {
            self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(0.5f, 0.5f));
        }

        self.alpha = 0.02f;
        [UIView commitAnimations];
    }
    else {
        self.alpha = 0.0f;
        [self done];
    }
    self.showStarted = nil;
}
  • 3.HUD指示器的实现
- (void)updateIndicators 
{

    // 读源码的时候,类似这种局部变量直接忽略,等代码用到它,我们再"懒加载"
    BOOL isActivityIndicator = [indicator isKindOfClass:[UIActivityIndicatorView class]];
    BOOL isRoundIndicator = [indicator isKindOfClass:[MBRoundProgressView class]];

    // 如果模式是MBProgressHUDModeIndeterminate,将使用系统自带的菊花系列指示器
    if (mode == MBProgressHUDModeIndeterminate) {
            // 再看回最上面的两条语句                
              // 初始化的时候进来,indicator是空的,对空对象发送消息返回的布尔值是NO
            // 因为在初始化完毕后,用户可能会设置mode属性,那时还会进入这个方法,所以这两个布尔变量除了第一次以外是有用的
        if (!isActivityIndicator) {
            // 默认第一次会进入到这里,对nil发送消息不会发生什么事
            // 为什么要removeFromSuperview呢,因为这方法并不会只进入一次
            // 不排除有些情况下先改变了mode到其他模式,之后又改回来了,这时候如果不移除
            // MBProgressHUD就会残留子控件在subviews里,虽然界面并不会显示它
            [indicator removeFromSuperview];
            // 使用系统自带的巨大白色菊花
            // 系统菊花有三种
            //typedef NS_ENUM(NSInteger, UIActivityIndicatorViewStyle) {
                    //    UIActivityIndicatorViewStyleWhiteLarge, // 大又白
                    //    UIActivityIndicatorViewStyleWhite, // 小白
                    //    UIActivityIndicatorViewStyleGray,  // 小灰
                //};
            self.indicator = MB_AUTORELEASE([[UIActivityIndicatorView alloc]
                                             initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]);
            [(UIActivityIndicatorView *)indicator startAnimating];
            [self addSubview:indicator];
        }
        // 系统菊花能设置颜色是从iOS5开始(NS_AVAILABLE_IOS(5_0)),这里用宏对手机版本进行了判断
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 50000
        [(UIActivityIndicatorView *)indicator setColor:self.activityIndicatorColor];
#endif
    }
            // 源码实现了两种自定义视图
            // 一种是MBBarProgressView(进度条),另一种是MBRoundProgressView(圆饼or圆环)

    else if (mode == MBProgressHUDModeDeterminateHorizontalBar) {
            // 进度条样式
        [indicator removeFromSuperview];
        self.indicator = MB_AUTORELEASE([[MBBarProgressView alloc] init]);
        [self addSubview:indicator];
    }

    else if (mode == MBProgressHUDModeDeterminate || mode == MBProgressHUDModeAnnularDeterminate) {
            // 这两种mode都产生MBRoundProgressView视图,MBRoundProgressView又分两种样式
            // 如果你设置了mode为MBProgressHUDModeDeterminate,那么流程是这样子的
            // 1)alloc init先生成系统的MBProgressHUDModeIndeterminate模式->
            // 2)设置了mode为饼图,触发KVO,又进入了updateIndicators方法->
            // 3)由于isRoundIndicator是No,产生饼状图

            // 如果设置了MBProgressHUDModeAnnularDeterminate,那么步骤比它多了一步,
            // 1)alloc init先生成系统的MBProgressHUDModeIndeterminate模式->
            // 2)设置了mode为圆环,触发KVO,又进入了updateIndicators方法->
            // 3)由于isRoundIndicator是No,产生饼状图->
            // 4)设置[(MBRoundProgressView *)indicator setAnnular:YES]触发MBRoundProgressView的
            // KVO进行重绘视图产生圆环图
        if (!isRoundIndicator) {
            // 个人认为这个isRoundIndicator变量纯属多余
            // isRoundIndicator为Yes的情况只有从MBProgressHUDModeDeterminate换成MBProgressHUDModeAnnularDeterminate
            // 或者MBProgressHUDModeAnnularDeterminate换成MBProgressHUDModeDeterminate
            // 而实际上这两种切换方式产生的视图都是圆环,这是由于没有让annular设置成No
            [indicator removeFromSuperview];
            self.indicator = MB_AUTORELEASE([[MBRoundProgressView alloc] init]);
            [self addSubview:indicator];
        }
        if (mode == MBProgressHUDModeAnnularDeterminate) {
            [(MBRoundProgressView *)indicator setAnnular:YES];
        }
    }

    else if (mode == MBProgressHUDModeCustomView && customView != indicator) {
        // 自定义视图
        [indicator removeFromSuperview];
        self.indicator = customView;
        [self addSubview:indicator];
    }

   else if (mode == MBProgressHUDModeText) {
          // 只有文字的模式
        [indicator removeFromSuperview];
        self.indicator = nil;
    }
}

  • 4.HUD布局

  下面我们看一下MBProgressHUD的子控件布局,它的子控件包括指示器、label、detailLabel,还要注意的是MBProgressHUD的背景是整个屏幕的,当我们设置MBProgressHUD的backgroundColor,会发现确实是整个屏幕的颜色都改变了。下面我们看一下子控件的布局。

- (void)layoutSubviews 
{
    [super layoutSubviews];

    // MBProgressHUD是一个充满整个父控件的控件
    // 使得父控件的交互完全被屏蔽
    UIView *parent = self.superview;
    if (parent) {
        self.frame = parent.bounds;
    }
    CGRect bounds = self.bounds;

    .......

    // 如果用户设置了square属性,就会尽量让它显示成正方形
    if (square) {
    // totalSize为下图蓝色框框的size
        CGFloat max = MAX(totalSize.width, totalSize.height);
        if (max <= bounds.size.width - 2 * margin) {
            totalSize.width = max;
        }
        if (max <= bounds.size.height - 2 * margin) {
            totalSize.height = max;
        }
    }
    if (totalSize.width < minSize.width) {
        totalSize.width = minSize.width;
    }
    if (totalSize.height < minSize.height) {
        totalSize.height = minSize.height;
    }

    size = totalSize;
}

具体布局可以参考下面的布局,这个图是别人画的,具体参考文章和博客我下面会列出,谢谢他的分享。

布局示意图

  上图蓝色虚线部分代表子控件们能够展示的区域,其中宽度是被限制的,其中定义了maxWidth让3个子控件中的最大宽度都不得超过它。值得注意的是,源码并没设置最大高度,如果我们使用自定义的视图,高度够大就会使蓝色虚线部分的上下底超出屏幕范围,某种程度上来讲也是设计上的一种bug,但是毕竟还是不影响正常的使用,毕竟没有人会用很大的自定义视图。
  此外,绿色的label被限制为只能显示一行,黄色的detailLabel通过下面的代码来限制它不能超出屏幕上下。

// 计算出屏幕剩下的高度
// 其中减去了4个margin大小,保证了子空间和HUD的边距,HUD和屏幕的距离
CGFloat remainingHeight = bounds.size.height - totalSize.height - kPadding - 4 * margin;

// 将文字内容限制在这个size中,超出部分省略号
CGSize maxSize = CGSizeMake(maxWidth, remainingHeight);

CCGSize detailsLabelSize = MB_MULTILINE_TEXTSIZE(detailsLabel.text, detailsLabel.font, maxSize, detailsLabel.lineBreakMode);

// 7.0开始使用boundingRectWithSize:options:attributes:context:方法计算
// 7.0以前使用sizeWithFont:constrainedToSize:lineBreakMode:计算
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000
#define MB_MULTILINE_TEXTSIZE(text, font, maxSize, mode) [text length] > 0 ? [text \
boundingRectWithSize:maxSize options:(NSStringDrawingUsesLineFragmentOrigin) \
attributes:@{NSFontAttributeName:font} context:nil].size : CGSizeZero;
#else 
#define MB_MULTILINE_TEXTSIZE(text, font, maxSize, mode) [text length] > 0 ? [text \
sizeWithFont:font constrainedToSize:maxSize lineBreakMode:mode] : CGSizeZero;
#endif

下图是另一种没达到maxSize的情况。

没达到maxSize的情况

参考文章和博客

1. 源码笔记---MBProgressHUD

后记

  这一篇主要写的是MBProgressHUD的初始化方法以及动画方法,未完,待续~~~

古画
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,717评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,501评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,311评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,417评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,500评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,538评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,557评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,310评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,759评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,065评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,233评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,909评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,548评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,172评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,420评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,103评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,098评论 2 352

推荐阅读更多精彩内容

  • 版本记录 前言 在我们的app项目中,为了增加和用户很好的交互能力,通常都需要加一些提示图,比如说,当我们需要网络...
    刀客传奇阅读 1,295评论 0 1
  • 简介: MBProgressHUD是一个iOS插件类,当在后台线程进行工作时,它会显示一个带有指示器和/或标签的半...
    Leon_520阅读 1,956评论 0 5
  • 源码来源:gitHub源码 转载于: CocoaChina 来源:南峰子的技术博客 版本:0.9.1 MBPr...
    李小六_阅读 6,428评论 2 5
  • 【莴苣苔】农家的饭桌上,最使我怀念的是门口菜园里的莴苣苔。苏北乡里人家屋舍布局大致如此:正南为堂屋,朝东为锅屋,再...
    大城小虎阅读 1,194评论 0 0
  • 本篇核心以善听、善知;《捭阖》侧重说,而《反应》侧重听。 古之大化者,乃与无形俱生。反以观往,复以验来;反以知古,...
    墨非_II阅读 841评论 0 1