iOS | MBProgressHUD 介紹 & 源碼解析

簡介:

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

image.png

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的推薦方法。

  1. 將MBProgressHUD的pod條目添加到Podfile pod 'MBProgressHUD'
  2. 通過運(yùn)行安裝pod pod install。
  3. 在任何需要的地方都包含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.hMBProgressHUD.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)建到展示到指定視圖的流程分析了,最后做一個概括:

  1. 首先 MBProgressHUD會創(chuàng)建一個遮罩視圖, 遮罩視圖默認(rèn)背景透明, 尺寸和 指定顯示視圖一致
  2. 創(chuàng)建 HUD指示器視圖邊框,默認(rèn)毛玻璃模糊效果, 并在上面創(chuàng)建 label 等控件,用于展示用戶提示文字
  3. 創(chuàng)建 HUD-indicator指示器,就是我們看到的加載菊花,加載進(jìn)度條,或者??或 X
  4. 然后通過制定的動畫方式進(jìn)行展示給用戶


以上是個人對MBProgressHUD源碼以及加載流程的理解,如有錯誤之處,還望各路大俠給予指出!
MBProgressHUD源碼地址: https://github.com/jdg/MBProgressHUD
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容