iOS | MBProgressHUD 介绍 & 源码解析

简介:

MBProgressHUD是一个iOS插件类,当在后台线程进行工作时,它会显示一个带有指示器和/或标签的半透明HUD, 并且提供了多样的展示效果供我们使用.

iOS | MBProgressHUD 介绍 & 源码解析_第1张图片
image.png

github地址:https://github.com/jdg/MBProgressHUD

要求

MBProgressHUD适用于iOS 6+系统, 并ARC构建环境。需要用到得iOS框架(Xcode默认都已经包含):

  • Foundation.framework
  • UIKit.framework
  • CoreGraphics.framework

安装:

CocoaPods是向项目添加MBProgressHUD的推荐方法。

  1. 将MBProgressHUD的pod条目添加到Podfile pod 'MBProgressHUD'
  2. 通过运行安装pod pod install
  3. 在任何需要的地方都包含MBProgressHUD #import

用法

MBProgressHUD中的所有方法建议在主线程上使用它;

// 展示
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
     // Do something...
     dispatch_async(dispatch_get_main_queue(), ^{
         // 隐藏
         [MBProgressHUD hideHUDForView:self.view animated:YES];
     });
});

可以在任何视图或窗口上添加HUD, 不过, 避免将HUD添加到具有复杂视图层次结构上,比如UITableView或UICollectionView。它可能以意想不到的方式改变子视图,从而破坏HUD显示。
如果需要配置HUD,可以使用 + (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated方法

MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.mode = MBProgressHUDModeAnnularDeterminate;
hud.label.text = @"Loading";
[self doSomethingInBackgroundWithProgressCallback:^(float progress) {
    hud.progress = progress;
} completionCallback:^{
    [hud hideAnimated:YES];
}];

注意: 使用MBProgressHUD进行UI更新时, 始终在主线程上执行。

源码分析:

通过 github 下载MBProgressHUD源码, 会发现只有2个文件MBProgressHUD.hMBProgressHUD.m, .h 文件提供了可以访问的接口和属性,.m文件用于实现细节; 接下来我们就来分析一下它的源码以及实现逻辑:

1. 常用属性

首先我们来看一下.h文件对我们提供了哪些常用属性和方法, .h文件对外暴露了大量的样式属性, 可以很方便的使我们对HUD的外观进行设置, 清楚了这些属性的含义, 方便我们在后续分析源码时候可以更加清楚的知道它设置的意义;

1.1 MBProgressHUDMode

MBProgressHUD 提供了6种Mode 可供我们选择, 主要用于HUD的指示器展示样式效果, 我们在使用时候可以根据不同的场景设置对应 Mode, 显示出来的HUD指示器会有不同的效果, 6种 Mode分别是:普通模式(菊花)视图, 圆形进度条视图,水平进度条视图,环形进度视图,自定义视图,纯文字视图, 代码如下:

//  HUD模式
typedef NS_ENUM(NSInteger, MBProgressHUDMode) {
    /// 普通模式,菊花
    MBProgressHUDModeIndeterminate,
    /// 一个圆形的饼状的进度视图。
    MBProgressHUDModeDeterminate,
    /// 水平进度条
    MBProgressHUDModeDeterminateHorizontalBar,
    /// 环形进度条
    MBProgressHUDModeAnnularDeterminate,
    /// 显示自定义视图。
    MBProgressHUDModeCustomView,
    /// 仅显示标签。
    MBProgressHUDModeText
};
1.2 MBProgressHUDAnimation

MBProgressHUDAnimation用于设置在显示和隐藏HUD时候的动画效果,提供了4中效果: 默认样式, 出现时放大消失时缩小, 缩小样式, 放大样式,主要用于动画效果;代码如下:

// 显示和隐藏HUD的动画类型
typedef NS_ENUM(NSInteger, MBProgressHUDAnimation) {
    /// 不透明度动画(默认)
    MBProgressHUDAnimationFade,
    /// 不透明度+缩放动画(出现时放大,消失时缩小)
    MBProgressHUDAnimationZoom,
    /// 不透明度+缩放动画(缩小样式)
    MBProgressHUDAnimationZoomOut,
    /// 不透明度+缩放动画(放大样式)
    MBProgressHUDAnimationZoomIn
};
1.3 MBProgressHUDBackgroundStyle

MBProgressHUDBackgroundStyle用于设置HUD的背景样式,提供了2种样式纯色样式模糊(类似毛玻璃)样式, 代码如下:

// HUD背景风格
typedef NS_ENUM(NSInteger, MBProgressHUDBackgroundStyle) {
    /// 纯色背景
    MBProgressHUDBackgroundStyleSolidColor,
    /// 模糊背景
    MBProgressHUDBackgroundStyleBlur
};
1.5 常见属性

以下属性基本我们在自定义HUD的时候会用的到. 通过以下属性我们就可以使得HUD指示器按照我们想要展示的样式进行展示了, 属性具体含义,可以查看注释,代码如下:

// 代理,用于HDB隐藏回调监测
@property (weak, nonatomic) id delegate;

// 当隐藏的时候是否将HUD从其父视图中移除.默认NO
@property (assign, nonatomic) BOOL removeFromSuperViewOnHide;

// HUD操作模式, 默认是 MBProgressHUDModeIndeterminate.
@property (assign, nonatomic) MBProgressHUDMode mode;

// 指示器主题颜色(文字、指示器颜色,不包含背景)
@property (strong, nonatomic, nullable) UIColor *contentColor;

// 显示和隐藏HUD时应该使用的动画类型。
@property (assign, nonatomic) MBProgressHUDAnimation animationType;

// 相对于视图中心的边框偏移量
@property (assign, nonatomic) CGPoint offset;

// HUD边缘和HUD元素(标签、指示器或自定义视图)之间的距离。 默认是 20.f
@property (assign, nonatomic) CGFloat margin;

// HUD边框的最小尺寸
@property (assign, nonatomic) CGSize minSize;

// 如果可能的话,强制HUD尺寸相等。
@property (assign, nonatomic, getter = isSquare) BOOL square ;

// 当启用时,边框中心受到设备加速度计数据的轻微影响。默认NO
@property (assign, nonatomic, getter=areDefaultMotionEffectsEnabled) BOOL defaultMotionEffectsEnabled;

// 进度指示器的进度,从0.0到1.0。默认为0.0。
@property (assign, nonatomic) float progress;

// NSProgress对象将进度信息提供给进度指示器。
@property (strong, nonatomic, nullable) NSProgress *progressObject;

// HUD指示器框视图, 包含标签和指示器的视图(或自定义视图)。
@property (strong, nonatomic, readonly) MBBackgroundView * _Nullable bezelView;

// 视图覆盖整个HUD区域,放在bezelView后面。
@property (strong, nonatomic, readonly) MBBackgroundView * _Nullable backgroundView;

// 自定义视图
@property (strong, nonatomic, nullable) UIView *customView;

// 包含可选短消息的标签,将显示在活动指示器下方。HUD是自动调整大小
@property (strong, nonatomic, readonly) UILabel * _Nullable label;

// 一个标签,其中包含一个可选的详细信息消息,显示在标签文本消息下面。详细信息文本可以跨多行。
@property (strong, nonatomic, readonly) UILabel * _Nullable detailsLabel;

// 位于标签下方的按钮。仅当添加目标/操作时才可见。
@property (strong, nonatomic, readonly) UIButton * _Nullable button;

// 显示HUD的最小时间,默认是0
@property (assign, nonatomic) NSTimeInterval minShowTime;

// 在HUD隐藏后调用
@property (copy, nullable) MBProgressHUDCompletionBlock completionBlock;

// 设置延迟展示
@property (assign, nonatomic) NSTimeInterval graceTime;


2. 视图初始化

上面介绍了一些MBProgressHUD常见的属性,接下来我们分析一下它是如何创建并显示到屏幕上的:

我们可以通过它提供的类方法+ (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated来进行创建, 创建后它会返回一个HUD实例对象,并且会自动添加到指定视图上面,代码如下:

 // 创建一个新的HUD,将其添加到提供的视图并显示它
+ (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated {
    // 初始化一个HUD
    MBProgressHUD *hud = [[self alloc] initWithView:view];
    // 当隐藏的时候是否将HUD从其父视图中移除,设置 yes
    hud.removeFromSuperViewOnHide = YES;
    // 添加到指定视图上
    [view addSubview:hud];
    // 设置显示的动画效果
    [hud showAnimated:animated];
    return hud;
}
2.1 属性初始化

MBProgressHUD 自身继承自UIView, 他本身就是一个视图, 通过- (id)initWithView:(UIView *)view方法进行初始化,初始化时候需要指定显示在哪个 View 上,如果不指定或者为 nil,初始化会报错崩溃

// MBProgressHUD 初始化
- (id)initWithView:(UIView *)view {
    // View不能为空
    NSAssert(view, @"View must not be nil.");
    return [self initWithFrame:view.bounds];
}
- (instancetype)initWithFrame:(CGRect)frame {
    if ((self = [super initWithFrame:frame])) {
        [self commonInit];
    }
    return self;
}

接下来会调用 initWithFrame方法,并在内部调用commonInit方法进行属性初始化,代码如下:

- (void)commonInit {
    // 显示和隐藏HUD时使用的动画类型
    _animationType = MBProgressHUDAnimationFade;
    
    // HUD显示模式
    _mode = MBProgressHUDModeIndeterminate;
    
    // HUD边缘和HUD元素(标签、指示器或自定义视图)之间的距离,默认20
    _margin = 20.0f;
    
    // 边框中心受到设备加速度计数据的轻微影响。
    _defaultMotionEffectsEnabled = NO;
    
    // 指示器主题颜色(文字、指示器颜色,不包含背景)
    _contentColor = [UIColor colorWithWhite:0.f alpha:0.7f];
    
    // 背景透明
    self.opaque = NO;
    
    // 清除背景颜色(不是指示器框背景,是整个HUD视图层)
    self.backgroundColor = [UIColor clearColor];
    
    // 暂时让它隐形
    self.alpha = 0.0f;
    self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    self.layer.allowsGroupOpacity = NO;
    
     // 用于初始化背景视图 和 HUD的边框视图
    [self setupViews];
    
    // 更新指示器信息
    [self updateIndicators];
    
    // 注册通知
    [self registerForNotifications];
}

上述代码可以看到,它内部的很多属性都是在.h 文件都可以看的到, 这里主要是进行一些默认设置

2.2 初始化背景遮罩以及 HUD指示器框视图

在上面的初始化中会调用[self setupViews]方法来初始化遮罩背景视图HUD指示器框视图, 首先通过MBBackgroundView类创建一个遮罩视图,然后添加到 MBProgressHUD 根视图上,然后继续通过MBBackgroundView 类初始化一个 HUD指示器框视图,也添加到 MBProgressHUD 根视图上,代码如下:


- (void)setupViews {
    
    // 指示器颜色
    UIColor *defaultColor = self.contentColor;
    
    // 背景视图(遮罩层)
    MBBackgroundView *backgroundView = [[MBBackgroundView alloc] initWithFrame:self.bounds];
    // HUD背景风格
    backgroundView.style = MBProgressHUDBackgroundStyleSolidColor;
    // 清除背景颜色
    backgroundView.backgroundColor = [UIColor clearColor];
    // 自动调整尺寸
    backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    // 透明度
    backgroundView.alpha = 0.f;
    // 添加到MBProgressHUD的 View 上
    [self addSubview:backgroundView];
    _backgroundView = backgroundView;
    
    // HUD指示器框视图
    MBBackgroundView *bezelView = [MBBackgroundView new];
    // 关闭自动调整
    bezelView.translatesAutoresizingMaskIntoConstraints = NO;
    // 圆角
    bezelView.layer.cornerRadius = 5.f;
    // 透明度
    bezelView.alpha = 0.f;
    [self addSubview:bezelView];
    _bezelView = bezelView;
    
    //  HUD文字
    UILabel *label = [UILabel new];
    ...省略不重要代码...
    
    // HUD详情文字
    UILabel *detailsLabel = [UILabel new];
     ...省略不重要代码...
    
    // HUD上面的按钮
    UIButton *button = [MBProgressHUDRoundedButton buttonWithType:UIButtonTypeCustom];
     ...省略不重要代码...
    
    // 顶部间距视图
    UIView *topSpacer = [UIView new];
    ...
    
    // 底部间距视图
    UIView *bottomSpacer = [UIView new];
    ...
}
2.3 指示器初始化

上述方法仅仅创建了遮罩背景视图HUD指示器框视图, 但是 HUD指示器样式(比如菊花, 环形进度条)还未进行初始化,需要通过- (void)updateIndicators方法进行初始化并设置, 此方法中主要通过外部提供的MBProgressHUDMode属性(上面已经介绍过对应属性值) 来进行样式初始化:

- (void)updateIndicators {
    
    // 获取当前指示器
    UIView *indicator = self.indicator;
    // 判断是UIActivityIndicatorView类型(菊花)
    BOOL isActivityIndicator = [indicator isKindOfClass:[UIActivityIndicatorView class]];
    // 判断是否为 MBRoundProgressView类型
    BOOL isRoundIndicator = [indicator isKindOfClass:[MBRoundProgressView class]];
    
    // 根据 HUDMode 来初始化相应样式的指示器;
    MBProgressHUDMode mode = self.mode;
    // 普通样式(菊花)
    if (mode == MBProgressHUDModeIndeterminate) {
        if (!isActivityIndicator) {
            // Update to indeterminate indicator
            [indicator removeFromSuperview];
            indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
            [(UIActivityIndicatorView *)indicator startAnimating];
            [self.bezelView addSubview:indicator];
        }
    }
    // 水平进度条
    else if (mode == MBProgressHUDModeDeterminateHorizontalBar) {
        // Update to bar determinate indicator
        [indicator removeFromSuperview];
        indicator = [[MBBarProgressView alloc] init];
        [self.bezelView addSubview:indicator];
    }
    // 环形进度视图
    else if (mode == MBProgressHUDModeDeterminate || mode == MBProgressHUDModeAnnularDeterminate) {
        if (!isRoundIndicator) {
            // Update to determinante indicator
            [indicator removeFromSuperview];
            indicator = [[MBRoundProgressView alloc] init];
            [self.bezelView addSubview:indicator];
        }
        if (mode == MBProgressHUDModeAnnularDeterminate) {
            [(MBRoundProgressView *)indicator setAnnular:YES];
        }
    }
    // 自定义指示器视图
    else if (mode == MBProgressHUDModeCustomView && self.customView != indicator) {
        // Update custom view indicator
        [indicator removeFromSuperview];
        indicator = self.customView;
        [self.bezelView addSubview:indicator];
    }
    // 纯文本视图
    else if (mode == MBProgressHUDModeText) {
        [indicator removeFromSuperview];
        indicator = nil;
    }
    
    indicator.translatesAutoresizingMaskIntoConstraints = NO;
    self.indicator = indicator;
    
    // 进度设置
    if ([indicator respondsToSelector:@selector(setProgress:)]) {
        [(id)indicator setValue:@(self.progress) forKey:@"progress"];
    }
    
    [indicator setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisHorizontal];
    [indicator setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisVertical];
    
    // 更新颜色信息
    [self updateViewsForColor:self.contentColor];
    [self setNeedsUpdateConstraints];
}
2.4 注册通知

上述完成了MBProgressHUD中视图的初始化工作,最后会通过[self registerForNotifications];注册一个通知,用于解决状态栏方向发生改变情况;

- (void)registerForNotifications {
#if !TARGET_OS_TV
    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];

    [nc addObserver:self selector:@selector(statusBarOrientationDidChange:)
               name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];
#endif
}

以上代码完成了MBProgressHUD 视图的初始化创建工作,接下来我们看下他是如何进行展示的;

3. MBProgressHUD 展示

HUD的展示会调用- (void)showAnimated:(BOOL)animated方法来进行HUD展示, 外部可以设置graceTimer属性来决定是否延迟展示HUD, 代码如下:

// 显示
- (void)showAnimated:(BOOL)animated {
    // 主线程断言判断
    MBMainThreadAssert();
    
    // 定时器销毁
    [self.minShowTimer invalidate];
    self.useAnimation = animated;
    self.finished = NO;
    
    // 延迟展示,通过NSTimer定时器来延迟展示HUD
    if (self.graceTime > 0.0) {
        NSTimer *timer = [NSTimer timerWithTimeInterval:self.graceTime target:self selector:@selector(handleGraceTimer:) userInfo:nil repeats:NO];
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
        self.graceTimer = timer;
    } 
    // 立即显示HUD
    else {
        [self showUsingAnimation:self.useAnimation];
    }
}

上述方法会调用- (void)showUsingAnimation:(BOOL)animated方法,在下面的方法中, 会取消之前的动画,并设置运动效果,代码如下:

// 使用动画展示
- (void)showUsingAnimation:(BOOL)animated {
    // 取消之前的动画
    [self.bezelView.layer removeAllAnimations];
    [self.backgroundView.layer removeAllAnimations];

    // 取消延迟隐藏定时器
    [self.hideDelayTimer invalidate];
    
    // 开始时间
    self.showStarted = [NSDate date];
    self.alpha = 1.f;

    // 隐藏并重新显示附加了相同的NSProgress对象。
    [self setNSProgressDisplayLinkEnabled:YES];

    // 设置运动效果
    [self updateBezelMotionEffects];

    // yes 则使用动画展示
    if (animated) {
        [self animateIn:YES withType:self.animationType completion:NULL];
    } else {
        self.bezelView.alpha = 1.f;
        self.backgroundView.alpha = 1.f;
    }
}

最后会调用- (void)animateIn:(BOOL)animatingIn withType:(MBProgressHUDAnimation)type completion:(void(^)(BOOL finished))completion 来真正的实现动画, 在下面的方法中,系统会根据type (MBProgressHUDAnimation类型,文章一开始有介绍)属性设置的动画效果来执行动画,代码如下:

// 展示动画
- (void)animateIn:(BOOL)animatingIn withType:(MBProgressHUDAnimation)type completion:(void(^)(BOOL finished))completion {
    
    // 缩放动画
    if (type == MBProgressHUDAnimationZoom) {
        type = animatingIn ? MBProgressHUDAnimationZoomIn : MBProgressHUDAnimationZoomOut;
    }

    CGAffineTransform small = CGAffineTransformMakeScale(0.5f, 0.5f);
    CGAffineTransform large = CGAffineTransformMakeScale(1.5f, 1.5f);

    // 设置起始状态
    UIView *bezelView = self.bezelView;
    if (animatingIn && bezelView.alpha == 0.f && type == MBProgressHUDAnimationZoomIn) {
        bezelView.transform = small;
    } else if (animatingIn && bezelView.alpha == 0.f && type == MBProgressHUDAnimationZoomOut) {
        bezelView.transform = large;
    }

    // 执行动画
    dispatch_block_t animations = ^{
        if (animatingIn) {
            bezelView.transform = CGAffineTransformIdentity;
        } else if (!animatingIn && type == MBProgressHUDAnimationZoomIn) {
            bezelView.transform = large;
        } else if (!animatingIn && type == MBProgressHUDAnimationZoomOut) {
            bezelView.transform = small;
        }
        CGFloat alpha = animatingIn ? 1.f : 0.f;
        bezelView.alpha = alpha;
        self.backgroundView.alpha = alpha;
    };
    
    [UIView animateWithDuration:0.3 delay:0. usingSpringWithDamping:1.f initialSpringVelocity:0.f options:UIViewAnimationOptionBeginFromCurrentState animations:animations completion:completion];
}

4. 总结

上述就是 MBProgressHUD从创建到展示到指定视图的流程分析了,最后做一个概括:

  1. 首先 MBProgressHUD会创建一个遮罩视图, 遮罩视图默认背景透明, 尺寸和 指定显示视图一致
  2. 创建 HUD指示器视图边框,默认毛玻璃模糊效果, 并在上面创建 label 等控件,用于展示用户提示文字
  3. 创建 HUD-indicator指示器,就是我们看到的加载菊花,加载进度条,或者✔️或 X
  4. 然后通过制定的动画方式进行展示给用户


以上是个人对MBProgressHUD源码以及加载流程的理解,如有错误之处,还望各路大侠给予指出!
MBProgressHUD源码地址: https://github.com/jdg/MBProgressHUD

你可能感兴趣的:(iOS | MBProgressHUD 介绍 & 源码解析)