iOS | MBProgressHUD 介绍 & 源码解析
简介:
MBProgressHUD
是一个iOS插件类,当在后台线程进行工作时,它会显示一个带有指示器和/或标签的半透明HUD, 并且提供了多样的展示效果供我们使用.
github地址:https://github.com/jdg/MBProgressHUD
要求
MBProgressHUD
适用于iOS 6+
系统, 并ARC构建环境。需要用到得iOS框架(Xcode默认都已经包含):
- Foundation.framework
- UIKit.framework
- CoreGraphics.framework
安装:
CocoaPods是向项目添加MBProgressHUD的推荐方法。
- 将MBProgressHUD的pod条目添加到Podfile
pod 'MBProgressHUD'
- 通过运行安装pod
pod install
。 - 在任何需要的地方都包含MBProgressHUD
#import <MBProgressHUD.h>
。
用法
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.h
和MBProgressHUD.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<MBProgressHUDDelegate> 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
从创建到展示到指定视图的流程分析了,最后做一个概括:
- 首先 MBProgressHUD会创建一个
遮罩视图
,遮罩视图
默认背景透明, 尺寸和 指定显示视图一致 - 创建
HUD指示器视图边框
,默认毛玻璃模糊效果, 并在上面创建 label 等控件,用于展示用户提示文字 - 创建
HUD-indicator指示器
,就是我们看到的加载菊花
,加载进度条
,或者✔️
或 X - 然后通过制定的动画方式进行展示给用户