簡介:
MBProgressHUD是一個iOS插件類,當(dāng)在后臺線程進(jìn)行工作時,它會顯示一個帶有指示器和/或標(biāo)簽的半透明HUD, 并且提供了多樣的展示效果供我們使用.

github地址:https://github.com/jdg/MBProgressHUD
要求
MBProgressHUD適用于iOS 6+系統(tǒng), 并ARC構(gòu)建環(huán)境。需要用到得iOS框架(Xcode默認(rèn)都已經(jīng)包含):
- Foundation.framework
- UIKit.framework
- CoreGraphics.framework
安裝:
CocoaPods是向項(xiàng)目添加MBProgressHUD的推薦方法。
- 將MBProgressHUD的pod條目添加到Podfile
pod 'MBProgressHUD' - 通過運(yùn)行安裝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添加到具有復(fù)雜視圖層次結(jié)構(gòu)上,比如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進(jìn)行UI更新時, 始終在主線程上執(zhí)行。
源碼分析:
通過 github 下載MBProgressHUD源碼, 會發(fā)現(xiàn)只有2個文件MBProgressHUD.h和MBProgressHUD.m, .h 文件提供了可以訪問的接口和屬性,.m文件用于實(shí)現(xiàn)細(xì)節(jié); 接下來我們就來分析一下它的源碼以及實(shí)現(xiàn)邏輯:
1. 常用屬性
首先我們來看一下.h文件對我們提供了哪些常用屬性和方法, .h文件對外暴露了大量的樣式屬性, 可以很方便的使我們對HUD的外觀進(jìn)行設(shè)置, 清楚了這些屬性的含義, 方便我們在后續(xù)分析源碼時候可以更加清楚的知道它設(shè)置的意義;
1.1 MBProgressHUDMode
MBProgressHUD 提供了6種Mode 可供我們選擇, 主要用于HUD的指示器展示樣式效果, 我們在使用時候可以根據(jù)不同的場景設(shè)置對應(yīng) Mode, 顯示出來的HUD指示器會有不同的效果, 6種 Mode分別是:普通模式(菊花)視圖, 圓形進(jìn)度條視圖,水平進(jìn)度條視圖,環(huán)形進(jìn)度視圖,自定義視圖,純文字視圖, 代碼如下:
// HUD模式
typedef NS_ENUM(NSInteger, MBProgressHUDMode) {
/// 普通模式,菊花
MBProgressHUDModeIndeterminate,
/// 一個圓形的餅狀的進(jìn)度視圖。
MBProgressHUDModeDeterminate,
/// 水平進(jìn)度條
MBProgressHUDModeDeterminateHorizontalBar,
/// 環(huán)形進(jìn)度條
MBProgressHUDModeAnnularDeterminate,
/// 顯示自定義視圖。
MBProgressHUDModeCustomView,
/// 僅顯示標(biāo)簽。
MBProgressHUDModeText
};
1.2 MBProgressHUDAnimation
MBProgressHUDAnimation用于設(shè)置在顯示和隱藏HUD時候的動畫效果,提供了4中效果: 默認(rèn)樣式, 出現(xiàn)時放大消失時縮小, 縮小樣式, 放大樣式,主要用于動畫效果;代碼如下:
// 顯示和隱藏HUD的動畫類型
typedef NS_ENUM(NSInteger, MBProgressHUDAnimation) {
/// 不透明度動畫(默認(rèn))
MBProgressHUDAnimationFade,
/// 不透明度+縮放動畫(出現(xiàn)時放大,消失時縮小)
MBProgressHUDAnimationZoom,
/// 不透明度+縮放動畫(縮小樣式)
MBProgressHUDAnimationZoomOut,
/// 不透明度+縮放動畫(放大樣式)
MBProgressHUDAnimationZoomIn
};
1.3 MBProgressHUDBackgroundStyle
MBProgressHUDBackgroundStyle用于設(shè)置HUD的背景樣式,提供了2種樣式純色樣式和模糊(類似毛玻璃)樣式, 代碼如下:
// HUD背景風(fēng)格
typedef NS_ENUM(NSInteger, MBProgressHUDBackgroundStyle) {
/// 純色背景
MBProgressHUDBackgroundStyleSolidColor,
/// 模糊背景
MBProgressHUDBackgroundStyleBlur
};
1.5 常見屬性
以下屬性基本我們在自定義HUD的時候會用的到. 通過以下屬性我們就可以使得HUD指示器按照我們想要展示的樣式進(jìn)行展示了, 屬性具體含義,可以查看注釋,代碼如下:
// 代理,用于HDB隱藏回調(diào)監(jiān)測
@property (weak, nonatomic) id<MBProgressHUDDelegate> delegate;
// 當(dāng)隱藏的時候是否將HUD從其父視圖中移除.默認(rèn)NO
@property (assign, nonatomic) BOOL removeFromSuperViewOnHide;
// HUD操作模式, 默認(rèn)是 MBProgressHUDModeIndeterminate.
@property (assign, nonatomic) MBProgressHUDMode mode;
// 指示器主題顏色(文字、指示器顏色,不包含背景)
@property (strong, nonatomic, nullable) UIColor *contentColor;
// 顯示和隱藏HUD時應(yīng)該使用的動畫類型。
@property (assign, nonatomic) MBProgressHUDAnimation animationType;
// 相對于視圖中心的邊框偏移量
@property (assign, nonatomic) CGPoint offset;
// HUD邊緣和HUD元素(標(biāo)簽、指示器或自定義視圖)之間的距離。 默認(rèn)是 20.f
@property (assign, nonatomic) CGFloat margin;
// HUD邊框的最小尺寸
@property (assign, nonatomic) CGSize minSize;
// 如果可能的話,強(qiáng)制HUD尺寸相等。
@property (assign, nonatomic, getter = isSquare) BOOL square ;
// 當(dāng)啟用時,邊框中心受到設(shè)備加速度計(jì)數(shù)據(jù)的輕微影響。默認(rèn)NO
@property (assign, nonatomic, getter=areDefaultMotionEffectsEnabled) BOOL defaultMotionEffectsEnabled;
// 進(jìn)度指示器的進(jìn)度,從0.0到1.0。默認(rèn)為0.0。
@property (assign, nonatomic) float progress;
// NSProgress對象將進(jìn)度信息提供給進(jìn)度指示器。
@property (strong, nonatomic, nullable) NSProgress *progressObject;
// HUD指示器框視圖, 包含標(biāo)簽和指示器的視圖(或自定義視圖)。
@property (strong, nonatomic, readonly) MBBackgroundView * _Nullable bezelView;
// 視圖覆蓋整個HUD區(qū)域,放在bezelView后面。
@property (strong, nonatomic, readonly) MBBackgroundView * _Nullable backgroundView;
// 自定義視圖
@property (strong, nonatomic, nullable) UIView *customView;
// 包含可選短消息的標(biāo)簽,將顯示在活動指示器下方。HUD是自動調(diào)整大小
@property (strong, nonatomic, readonly) UILabel * _Nullable label;
// 一個標(biāo)簽,其中包含一個可選的詳細(xì)信息消息,顯示在標(biāo)簽文本消息下面。詳細(xì)信息文本可以跨多行。
@property (strong, nonatomic, readonly) UILabel * _Nullable detailsLabel;
// 位于標(biāo)簽下方的按鈕。僅當(dāng)添加目標(biāo)/操作時才可見。
@property (strong, nonatomic, readonly) UIButton * _Nullable button;
// 顯示HUD的最小時間,默認(rèn)是0
@property (assign, nonatomic) NSTimeInterval minShowTime;
// 在HUD隱藏后調(diào)用
@property (copy, nullable) MBProgressHUDCompletionBlock completionBlock;
// 設(shè)置延遲展示
@property (assign, nonatomic) NSTimeInterval graceTime;
2. 視圖初始化
上面介紹了一些MBProgressHUD常見的屬性,接下來我們分析一下它是如何創(chuàng)建并顯示到屏幕上的:
我們可以通過它提供的類方法+ (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated來進(jìn)行創(chuàng)建, 創(chuàng)建后它會返回一個HUD實(shí)例對象,并且會自動添加到指定視圖上面,代碼如下:
// 創(chuàng)建一個新的HUD,將其添加到提供的視圖并顯示它
+ (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated {
// 初始化一個HUD
MBProgressHUD *hud = [[self alloc] initWithView:view];
// 當(dāng)隱藏的時候是否將HUD從其父視圖中移除,設(shè)置 yes
hud.removeFromSuperViewOnHide = YES;
// 添加到指定視圖上
[view addSubview:hud];
// 設(shè)置顯示的動畫效果
[hud showAnimated:animated];
return hud;
}
2.1 屬性初始化
MBProgressHUD 自身繼承自UIView, 他本身就是一個視圖, 通過- (id)initWithView:(UIView *)view方法進(jìn)行初始化,初始化時候需要指定顯示在哪個 View 上,如果不指定或者為 nil,初始化會報(bào)錯崩潰
// 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;
}
接下來會調(diào)用 initWithFrame方法,并在內(nèi)部調(diào)用commonInit方法進(jìn)行屬性初始化,代碼如下:
- (void)commonInit {
// 顯示和隱藏HUD時使用的動畫類型
_animationType = MBProgressHUDAnimationFade;
// HUD顯示模式
_mode = MBProgressHUDModeIndeterminate;
// HUD邊緣和HUD元素(標(biāo)簽、指示器或自定義視圖)之間的距離,默認(rèn)20
_margin = 20.0f;
// 邊框中心受到設(shè)備加速度計(jì)數(shù)據(jù)的輕微影響。
_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];
}
上述代碼可以看到,它內(nèi)部的很多屬性都是在.h 文件都可以看的到, 這里主要是進(jìn)行一些默認(rèn)設(shè)置
2.2 初始化背景遮罩以及 HUD指示器框視圖
在上面的初始化中會調(diào)用[self setupViews]方法來初始化遮罩背景視圖和 HUD指示器框視圖, 首先通過MBBackgroundView類創(chuàng)建一個遮罩視圖,然后添加到 MBProgressHUD 根視圖上,然后繼續(xù)通過MBBackgroundView 類初始化一個 HUD指示器框視圖,也添加到 MBProgressHUD 根視圖上,代碼如下:
- (void)setupViews {
// 指示器顏色
UIColor *defaultColor = self.contentColor;
// 背景視圖(遮罩層)
MBBackgroundView *backgroundView = [[MBBackgroundView alloc] initWithFrame:self.bounds];
// HUD背景風(fēng)格
backgroundView.style = MBProgressHUDBackgroundStyleSolidColor;
// 清除背景顏色
backgroundView.backgroundColor = [UIColor clearColor];
// 自動調(diào)整尺寸
backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
// 透明度
backgroundView.alpha = 0.f;
// 添加到MBProgressHUD的 View 上
[self addSubview:backgroundView];
_backgroundView = backgroundView;
// HUD指示器框視圖
MBBackgroundView *bezelView = [MBBackgroundView new];
// 關(guān)閉自動調(diào)整
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 指示器初始化
上述方法僅僅創(chuàng)建了遮罩背景視圖和 HUD指示器框視圖, 但是 HUD指示器樣式(比如菊花, 環(huán)形進(jìn)度條)還未進(jìn)行初始化,需要通過- (void)updateIndicators方法進(jìn)行初始化并設(shè)置, 此方法中主要通過外部提供的MBProgressHUDMode屬性(上面已經(jīng)介紹過對應(yīng)屬性值) 來進(jìn)行樣式初始化:
- (void)updateIndicators {
// 獲取當(dāng)前指示器
UIView *indicator = self.indicator;
// 判斷是UIActivityIndicatorView類型(菊花)
BOOL isActivityIndicator = [indicator isKindOfClass:[UIActivityIndicatorView class]];
// 判斷是否為 MBRoundProgressView類型
BOOL isRoundIndicator = [indicator isKindOfClass:[MBRoundProgressView class]];
// 根據(jù) HUDMode 來初始化相應(yīng)樣式的指示器;
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];
}
}
// 水平進(jìn)度條
else if (mode == MBProgressHUDModeDeterminateHorizontalBar) {
// Update to bar determinate indicator
[indicator removeFromSuperview];
indicator = [[MBBarProgressView alloc] init];
[self.bezelView addSubview:indicator];
}
// 環(huán)形進(jìn)度視圖
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;
// 進(jìn)度設(shè)置
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];注冊一個通知,用于解決狀態(tài)欄方向發(fā)生改變情況;
- (void)registerForNotifications {
#if !TARGET_OS_TV
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:@selector(statusBarOrientationDidChange:)
name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];
#endif
}
以上代碼完成了MBProgressHUD 視圖的初始化創(chuàng)建工作,接下來我們看下他是如何進(jìn)行展示的;
3. MBProgressHUD 展示
HUD的展示會調(diào)用- (void)showAnimated:(BOOL)animated方法來進(jìn)行HUD展示, 外部可以設(shè)置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];
}
}
上述方法會調(diào)用- (void)showUsingAnimation:(BOOL)animated方法,在下面的方法中, 會取消之前的動畫,并設(shè)置運(yùn)動效果,代碼如下:
// 使用動畫展示
- (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];
// 設(shè)置運(yùn)動效果
[self updateBezelMotionEffects];
// yes 則使用動畫展示
if (animated) {
[self animateIn:YES withType:self.animationType completion:NULL];
} else {
self.bezelView.alpha = 1.f;
self.backgroundView.alpha = 1.f;
}
}
最后會調(diào)用- (void)animateIn:(BOOL)animatingIn withType:(MBProgressHUDAnimation)type completion:(void(^)(BOOL finished))completion 來真正的實(shí)現(xiàn)動畫, 在下面的方法中,系統(tǒng)會根據(jù)type (MBProgressHUDAnimation類型,文章一開始有介紹)屬性設(shè)置的動畫效果來執(zhí)行動畫,代碼如下:
// 展示動畫
- (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);
// 設(shè)置起始狀態(tài)
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;
}
// 執(zhí)行動畫
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. 總結(jié)
上述就是 MBProgressHUD從創(chuàng)建到展示到指定視圖的流程分析了,最后做一個概括:
- 首先 MBProgressHUD會創(chuàng)建一個
遮罩視圖,遮罩視圖默認(rèn)背景透明, 尺寸和 指定顯示視圖一致 - 創(chuàng)建
HUD指示器視圖邊框,默認(rèn)毛玻璃模糊效果, 并在上面創(chuàng)建 label 等控件,用于展示用戶提示文字 - 創(chuàng)建
HUD-indicator指示器,就是我們看到的加載菊花,加載進(jìn)度條,或者??或 X - 然后通過制定的動畫方式進(jìn)行展示給用戶