MBProgressHUD的基本使用

MBProgressHUD是一個顯示HUD窗口的第三方類庫,用於在執行一些後臺任務時,在程序中顯示一個表示進度的loading視圖和兩個可選的文本提示的HUD窗口。我想最可能是應用在加載網絡數據的時候。其實蘋果官方本身有一個帶有此功能的類UIProgressHUD,只不過它是私有的,如今不讓用。至於實際的效果,能夠看看github上工程給出的幾張圖例(貌似我這常常沒法單獨打開圖片,因此就不在這貼圖片了),也能夠運行一下Demo。git

具體用法咱們就很少說了,參考github上的說明就能用得很順的。本文主要仍是從源碼的角度來分析一下它的具體實現。github

 

模式數組

在分析實現代碼以前,咱們先來看看MBProgressHUD中定義的MBProgressHUDMode枚舉。它用來表示HUD窗口的模式,即咱們從效果圖中看到的幾種顯示樣式。其具體定義以下:網絡

typedef enum {
    // 使用UIActivityIndicatorView來顯示進度,這是默認值
    MBProgressHUDModeIndeterminate,
 
    // 使用一個圓形餅圖來做爲進度視圖
    MBProgressHUDModeDeterminate,
 
    // 使用一個水平進度條
    MBProgressHUDModeDeterminateHorizontalBar,
 
    // 使用圓環做爲進度條
    MBProgressHUDModeAnnularDeterminate,
 
    // 顯示一個自定義視圖,經過這種方式,能夠顯示一個正確或錯誤的提示圖
    MBProgressHUDModeCustomView,
 
    // 只顯示文本
    MBProgressHUDModeText
 
} MBProgressHUDMode;
 異步

外觀經過設置MBProgressHUD的模式,咱們可使用MBProgressHUD自定義的表示進度的視圖來知足咱們的需求,也能夠自定義這個進度視圖,固然還能夠只顯示文本。在下面咱們會討論源碼中是如何使用這幾個值的。async

咱們先來了解一下MBProgressHUD的基本組成。一個MBProgressHUD視圖主要由四個部分組成:ide

  1. loading動畫視圖(在此作個統稱,固然這個區域能夠是自定義的一個UIImageView視圖)。這個視圖由咱們設定的模式值決定,能夠是菊花、進度條,也能夠是咱們自定義的視圖;佈局

  2. 標題文本框(label):主要用於顯示提示的主題信息。這個文本框是可選的,一般位於loading動畫視圖的下面,且它是單行顯示。它會根據labelText屬性來自適應文本的大小(有一個長度上限),若是過長,則超出的部分會顯示爲」…「;post

  3. 詳情文本框(detailsLabel)。若是以爲標題不夠詳細,或者有附屬信息,就能夠將詳細信息放在這裏面顯示。該文本框對應的是顯示detailsLabelText屬性的值,它是能夠多行顯示的。另外,詳情的顯示還依賴於labelText屬性的設置,只有labelText屬性被設置了,且不爲空串,纔會顯示detailsLabel;字體

  4. HUD背景框。主要是做爲上面三個部分的一個背景,用來突出上面三部分。

爲了讓咱們更好地自定義這幾個部分,MBProgressHUD還提供了一些屬性,咱們簡單瞭解一下:

// 背景框的透明度,默認值是0.8
@property (assign) float opacity;
 
// 背景框的顏色
// 須要注意的是若是設置了這個屬性,則opacity屬性會失效,即不會有半透明效果
@property (MB_STRONG) UIColor *color;
 
// 背景框的圓角半徑。默認值是10.0
@property (assign) float cornerRadius;
 
// 標題文本的字體及顏色
@property (MB_STRONG) UIFont* labelFont;
@property (MB_STRONG) UIColor* labelColor;
 
// 詳情文本的字體及顏色
@property (MB_STRONG) UIFont* detailsLabelFont;
@property (MB_STRONG) UIColor* detailsLabelColor;
 
// 菊花的顏色,默認是白色
@property (MB_STRONG) UIColor *activityIndicatorColor;


另外還有一個比較有意思的屬性是dimBackground,用於爲HUD窗口的視圖區域覆蓋上一層徑向漸變(radial gradient)層,其定義以下:經過以上屬性,咱們能夠根據本身的須要來設置這幾個部分的外觀。

@property (assign) BOOL dimBackground;

- (void)drawRect:(CGRect)rect {
 
    ...
 
    if (self.dimBackground) {
        //Gradient colours
        size_t gradLocationsNum = 2;
        CGFloat gradLocations[2] = {0.0f, 1.0f};
        CGFloat gradColors[8] = {0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.75f}; 
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, gradColors, gradLocations, gradLocationsNum);
        CGColorSpaceRelease(colorSpace);
 
        //Gradient center
        CGPoint gradCenter= CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2);
        //Gradient radius
        float gradRadius = MIN(self.bounds.size.width , self.bounds.size.height) ;
 
        // 由中心向四周繪製漸變
        CGContextDrawRadialGradient (context, gradient, gradCenter,
                                     0, gradCenter, gradRadius,
                                     kCGGradientDrawsAfterEndLocation);
        CGGradientRelease(gradient);
    }
    ... 
}

讓咱們來看看經過它,MBProgressHUD都作了些什麼。代碼以下:

這段代碼由中心向MBProgressHUD視圖的四周繪製了一個漸變層。固然,這裏的顏色值是寫死的,咱們沒法自行定義。有興趣的話,你們能夠將這個屬性設置爲YES,看看實際的效果。

建立、佈局與繪製

除了繼承自UIView的-initWithFrame:初始化方法,MBProgressHUD還爲咱們提供了兩個初始化方法,以下所示:

- (id)initWithWindow:(UIWindow *)window;
 
- (id)initWithView:(UIView *)view;


MBProgressHUD提供了幾個屬性,可讓咱們控制HUD的佈局,這些屬性主要有如下幾個:這兩個方法分別傳入一個UIWindow對象和一個UIView對象。傳入的視圖對象僅僅是作爲MBProgressHUD視圖定義其frame屬性的參照,而不會直接將MBProgressHUD視圖添加到傳入的視圖對象上。這個添加操做還得咱們自行處理(固然,MBProgressHUD還提供了幾個便捷的類方法,咱們下面會說明)。

// HUD相對於父視圖中心點的x軸偏移量和y軸偏移量
@property (assign) float xOffset;
@property (assign) float yOffset;
 
// HUD各元素與HUD邊緣的間距
@property (assign) float margin;
 
// HUD背景框的最小大小
@property (assign) CGSize minSize;
 
// HUD的實際大小
@property (atomic, assign, readonly) CGSize size;
 
// 是否強制HUD背景框寬高相等
@property (assign, getter = isSquare) BOOL square;

 

須要注意的是,MBProgressHUD視圖會充滿其父視圖的frame內,爲此,在MBProgressHUD的layoutSubviews方法中,還專門作了處理,以下代碼所示:

 

 


在佈局的過程當中,會先根據咱們要顯示的視圖計算出容納這些視圖所須要的總的寬度和高度。固然,會設置一個最大值。咱們截取其中一段來看看:也所以,當MBProgressHUD顯示時,它也會屏蔽父視圖的各類交互操做。

CGRect bounds = self.bounds;

 

...

 

CGFloat remainingHeight = bounds.size.height - totalSize.height - kPadding - 4 * margin; 

CGSize maxSize = CGSizeMake(maxWidth, remainingHeight);

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

totalSize.width = MAX(totalSize.width, detailsLabelSize.width);

totalSize.height += detailsLabelSize.height;

if (detailsLabelSize.height > 0.f && (indicatorF.size.height > 0.f || labelSize.height > 0.f)) {

    totalSize.height += kPadding;

}

 

totalSize.width += 2 * margin;

totalSize.height += 2 * margin;

在上面的佈局代碼中,主要是處理了loading動畫視圖、標題文本框和詳情文本框,而HUD背景框主要是在drawRect:中來繪製的。背景框的繪製代碼以下:以後,就開始從上到下放置各個視圖。在佈局代碼的最後,計算了一個size值,這是爲後面繪製背景框作準備的。

// Center HUD

CGRect allRect = self.bounds;

// Draw rounded HUD backgroud rect

CGRect boxRect = CGRectMake(round((allRect.size.width - size.width) / 2) + self.xOffset,

                            round((allRect.size.height - size.height) / 2) + self.yOffset, size.width, size.height);

float radius = self.cornerRadius;

CGContextBeginPath(context);

CGContextMoveToPoint(context, CGRectGetMinX(boxRect) + radius, CGRectGetMinY(boxRect));

CGContextAddArc(context, CGRectGetMaxX(boxRect) - radius, CGRectGetMinY(boxRect) + radius, radius, 3 * (float)M_PI / 2, 0, 0);

CGContextAddArc(context, CGRectGetMaxX(boxRect) - radius, CGRectGetMaxY(boxRect) - radius, radius, 0, (float)M_PI / 2, 0);

CGContextAddArc(context, CGRectGetMinX(boxRect) + radius, CGRectGetMaxY(boxRect) - radius, radius, (float)M_PI / 2, (float)M_PI, 0);

CGContextAddArc(context, CGRectGetMinX(boxRect) + radius, CGRectGetMinY(boxRect) + radius, radius, (float)M_PI, 3 * (float)M_PI / 2, 0);

CGContextClosePath(context);

CGContextFillPath(context);

 

這是最日常的繪製操做,在此很少作解釋。

咱們上面講過MBProgressHUD提供了幾種窗口模式,這幾種模式的主要區別在於loading動畫視圖的展現。默認狀況下,使用的是菊花(MBProgressHUDModeIndeterminate)。咱們能夠經過設置如下屬性,來改變loading動畫視圖:

1

@property (assign) MBProgressHUDMode mode;

對於其它幾種模式,MBProgressHUD專門咱們提供了幾個視圖類。若是是進度條模式(MBProgressHUDModeDeterminateHorizontalBar),則使用的是MBBarProgressView類;若是是餅圖模式(MBProgressHUDModeDeterminate)或環形模式(MBProgressHUDModeAnnularDeterminate),則使用的是MBRoundProgressView類。上面這兩個類的主要操做就是在drawRect:中根據一些進度參數來繪製形狀,你們能夠本身詳細看一下。

固然,咱們還能夠自定義loading動畫視圖,此時選擇的模式是MBProgressHUDModeCustomView。或者不顯示loading動畫視圖,而只顯示文本框(MBProgressHUDModeText)。

具體顯示哪種loading動畫視圖,是在-updateIndicators方法中來處理的,其實現以下所示:

- (void)updateIndicators {

 

    BOOL isActivityIndicator = [indicator isKindOfClass:[UIActivityIndicatorView class]];

    BOOL isRoundIndicator = [indicator isKindOfClass:[MBRoundProgressView class]];

 

    if (mode == MBProgressHUDModeIndeterminate) {

        ...

    }

    else if (mode == MBProgressHUDModeDeterminateHorizontalBar) {

        // Update to bar determinate indicator

        [indicator removeFromSuperview];

        self.indicator = MB_AUTORELEASE([[MBBarProgressView alloc] init]);

        [self addSubview:indicator];

    }

    else if (mode == MBProgressHUDModeDeterminate || mode == MBProgressHUDModeAnnularDeterminate) {

        if (!isRoundIndicator) {

            ...

        }

        if (mode == MBProgressHUDModeAnnularDeterminate) {

            [(MBRoundProgressView *)indicator setAnnular:YES];

        }

    

    else if (mode == MBProgressHUDModeCustomView && customView != indicator) {

        ...

    else if (mode == MBProgressHUDModeText) {

        ...

    }

}

 

MBRoundProgressView爲咱們提供了豐富的顯示與隱藏HUD窗口的。在分析這些方法以前,咱們先來看看MBProgressHUD爲顯示與隱藏提供的一些屬性:顯示與隱藏

// HUD顯示和隱藏的動畫類型

@property (assign) MBProgressHUDAnimation animationType;

 

// HUD顯示的最短期。設置這個值是爲了不HUD顯示後當即被隱藏。默認值爲0

@property (assign) float minShowTime;

 

// 這個屬性設置了一個寬限期,它是在沒有顯示HUD窗口前被調用方法可能運行的時間。

// 若是被調用方法在寬限期內執行完,則HUD不會被顯示。

// 這主要是爲了不在執行很短的任務時,去顯示一個HUD窗口。

// 默認值是0。只有當任務狀態是已知時,才支持寬限期。具體咱們看實現代碼。

@property (assign) float graceTime;

 

// 這是一個標識位,標明執行的操做正在處理中。這個屬性是配合graceTime使用的。

// 若是沒有設置graceTime,則這個標識是沒有太大意義的。在使用showWhileExecuting:onTarget:withObject:animated:方法時,

// 會自動去設置這個屬性爲YES,其它狀況下都須要咱們本身手動設置。

@property (assign) BOOL taskInProgress;

 

// 隱藏時是否將HUD從父視圖中移除,默認是NO。

@property (assign) BOOL removeFromSuperViewOnHide;

 

// 進度指示器,從0.0到1.0,默認值爲0.0

@property (assign) float progress;

 

// 在HUD被隱藏後的回調

@property (copy) MBProgressHUDCompletionBlock completionBlock;

 

以上這些屬性都還好理解,可能須要注意的就是graceTime和taskInProgress的配合使用。在下面咱們將會看看這兩個屬性的用法。

對於顯示操做,最基本的就是-show:方法(其它幾個顯示方法都會調用該方法來顯示HUD窗口),咱們先來看看它的實現,

- (void)show:(BOOL)animated {

    useAnimation = animated;

    // If the grace time is set postpone the HUD display

    if (self.graceTime > 0.0) {

        self.graceTimer = [NSTimer scheduledTimerWithTimeInterval:self.graceTime target:self 

                           selector:@selector(handleGraceTimer:) userInfo:nil repeats:NO];

    

    // ... otherwise show the HUD imediately 

    else {

        [self showUsingAnimation:useAnimation];

    }

}

- (void)handleGraceTimer:(NSTimer *)theTimer {

    // Show the HUD only if the task is still running

    if (taskInProgress) {

        [self showUsingAnimation:useAnimation];

    }

}


能夠看到,只有在設置了taskInProgress標識位爲YES的狀況下,纔會去顯示HUD窗口。因此,若是咱們要本身調用-show:方法的話,須要酌情考慮設置taskInProgress標識位。能夠看到,若是咱們沒有設置graceTime屬性,則會當即顯示HUD;而若是設置了graceTime,則會建立一個定時器,並讓顯示操做延遲到graceTime所設定的時間再執行,而-handleGraceTimer:實現以下:

除了-show:方法之外,MBProgressHUD還爲咱們提供了一組顯示方法,可讓咱們在顯示HUD的同時,執行一些後臺任務,咱們在此主要介紹兩個。其中一個是-showWhileExecuting:onTarget:withObject:animated:,它是基於target-action方式的調用,在執行一個後臺任務時顯示HUD,等後臺任務執行完成後再隱藏HUD,具體實現以下所示:

- (void)showWhileExecuting:(SEL)method onTarget:(id)target withObject:(id)object animated:(BOOL)animated {

    methodForExecution = method;

    targetForExecution = MB_RETAIN(target);

    objectForExecution = MB_RETAIN(object); 

    // Launch execution in new thread

    self.taskInProgress = YES;

    [NSThread detachNewThreadSelector:@selector(launchExecution) toTarget:self withObject:nil];

    // Show HUD view

    [self show:animated];

}

 

- (void)launchExecution {

    @autoreleasepool {

#pragma clang diagnostic push

#pragma clang diagnostic ignored "-Warc-performSelector-leaks"

        // Start executing the requested task

        [targetForExecution performSelector:methodForExecution withObject:objectForExecution];

#pragma clang diagnostic pop

        // Task completed, update view in main thread (note: view operations should

        // be done only in the main thread)

        [self performSelectorOnMainThread:@selector(cleanUp) withObject:nil waitUntilDone:NO];

    }

}

而在異步調用方法-launchExecution中,線程首先是維護了本身的一個@autoreleasepool,因此在咱們本身的方法中,就不須要再去維護一個@autoreleasepool了。以後是去執行咱們的任務,在任務完成以後,再回去主線程去執行清理操做,並隱藏HUD窗口。能夠看到,-showWhileExecuting:onTarget:withObject:animated:首先將taskInProgress屬性設置爲YES,這樣在調用-show:方法時,即便設置了graceTime,也確保能在任務完成以前顯示HUD。而後開啓一個新線程,來異步執行咱們的後臺任務,最後去顯示HUD。

另外一個顯示方法是-showAnimated:whileExecutingBlock:onQueue:completionBlock:,它是基於GCD的調用,當block中的任務在指定的隊列中執行時,顯示HUD窗口,任務完成以後執行completionBlock操做,最後隱藏HUD窗口。咱們來看看它的具體實現:

- (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue

     completionBlock:(MBProgressHUDCompletionBlock)completion {

    self.taskInProgress = YES;

    self.completionBlock = completion;

    dispatch_async(queue, ^(void) {

        block();

        dispatch_async(dispatch_get_main_queue(), ^(void) {

            [self cleanUp];

        });

    });

    [self show:animated];

}


對於HUD的隱藏,MBProgressHUD提供了兩個方法,一個是-hide:,另外一個是-hide:afterDelay:,後者基於前者,因此咱們主要來看看-hide:的實現:這個方法也是首先將taskInProgress屬性設置爲YES,而後開啓一個線程去執行block任務,最後主線程去執行清理操做,並隱藏HUD窗口。

- (void)hide:(BOOL)animated {

    useAnimation = animated;

    // If the minShow time is set, calculate how long the hud was shown,

    // and pospone the hiding operation if necessary

    if (self.minShowTime > 0.0 && showStarted) {

        NSTimeInterval interv = [[NSDate date] timeIntervalSinceDate:showStarted];

        if (interv < self.minShowTime) {

            self.minShowTimer = [NSTimer scheduledTimerWithTimeInterval:(self.minShowTime - interv) target:self 

                                selector:@selector(handleMinShowTimer:) userInfo:nil repeats:NO];

            return;

        

    }

    // ... otherwise hide the HUD immediately

    [self hideUsingAnimation:useAnimation];

}


隱藏的實際操做主要是去作了些清理操做,包括根據設定的removeFromSuperViewOnHide值來執行是否從父視圖移除HUD窗口,以及執行completionBlock操做,還有就是執行代理的hudWasHidden:方法。這些操做是在私有方法-done裏面執行的,實現以下:咱們能夠看到,在設置了minShowTime屬性而且已經顯示了HUD窗口的狀況下,會去判斷顯示的時間是否小於minShowTime指定的時間,若是是,則會開啓一個定時器,等到顯示的時間到了minShowTime所指定的時間,纔會去隱藏HUD窗口;不然會直接去隱藏HUD窗口。

- (void)done {

    [NSObject cancelPreviousPerformRequestsWithTarget:self];

    isFinished = YES;

    self.alpha = 0.0f;

    if (removeFromSuperViewOnHide) {

        [self removeFromSuperview];

    }

#if NS_BLOCKS_AVAILABLE

    if (self.completionBlock) {

        self.completionBlock();

        self.completionBlock = NULL;

    }

#endif

    if ([delegate respondsToSelector:@selector(hudWasHidden:)]) {

        [delegate performSelector:@selector(hudWasHidden:) withObject:self];

    }

}

MBProgressHUD的一些主要的代碼差很少已經分析完了,最後還有些邊邊角角的地方,一塊兒來看看。其它

顯示和隱藏的便捷方法

除了上面描述的實例方法以外,MBProgressHUD還爲咱們提供了幾個便捷顯示和隱藏HUD窗口的方法,以下所示:

+ (MB_INSTANCETYPE)showHUDAddedTo:(UIView *)view animated:(BOOL)animated

 

+ (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated

 

+ (NSUInteger)hideAllHUDsForView:(UIView *)view animated:(BOOL)animated

 

部分屬性值的設置方法的簽名已經很能說明問題了,在此很少描述。

對於部分屬性(主要是」外觀」一節中針對菊花、標題文本框和詳情文本框的幾個屬性值),爲了在設置將這些屬性時修改對應視圖的屬性,並無直接爲每一個屬性生成一個setter,而是經過KVO來監聽這些屬性值的變化,再將這些值賦值給視圖的對應屬性,以下所示:

// 監聽的屬性數組

- (NSArray *)observableKeypaths {

    return [NSArray arrayWithObjects:@"mode", @"customView", @"labelText", @"labelFont", @"labelColor", ..., nil];

}

 

// 註冊KVO

- (void)registerForKVO {

    for (NSString *keyPath in [self observableKeypaths]) {

        [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:NULL];

    }

}

 

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {

    if (![NSThread isMainThread]) {

        [self performSelectorOnMainThread:@selector(updateUIForKeypath:) withObject:keyPath waitUntilDone:NO];

    else {

        [self updateUIForKeypath:keyPath];

    }

}

 

- (void)updateUIForKeypath:(NSString *)keyPath {

    if ([keyPath isEqualToString:@"mode"] || [keyPath isEqualToString:@"customView"] ||

        [keyPath isEqualToString:@"activityIndicatorColor"]) {

        [self updateIndicators];

    else if ([keyPath isEqualToString:@"labelText"]) {

        label.text = self.labelText;

    

 

    ...

 

    [self setNeedsLayout];

    [self setNeedsDisplay];

}


MBProgressHUD還爲咱們提供了一個代理MBProgressHUDDelegate,這個代理中只提供了一個方法,即:代理

- (void)hudWasHidden:(MBProgressHUD *)hud;
 

問題這個代理方法是在隱藏HUD窗口後調用,若是此時咱們須要在咱們本身的實現中執行某些操做,則能夠實現這個方法。

MBProgressHUD爲咱們提供了一個HUD窗口的很好的實現,不過我的在使用過程當中,以爲它給咱們提供的交互功能太少。其代理只提供了一個-hudWasHidden:方法,並且咱們也沒法經過點擊HUD來執行一些操做。在現實的需求中,可能存在這種狀況:好比一個網絡操做,在發送請求等待響應的過程當中,咱們會顯示一個HUD窗口以顯示一個loading框。但若是咱們想在等待響應的過程當中,在當前視圖中取消這個網絡請求,就沒有相應的處理方式,MBProgressHUD沒有爲咱們提供這樣的交互操做。固然這時候,咱們能夠根據本身的需求來修改源碼。

與SVProgressHUD的對比

與MBProgressHUD相似,SVProgressHUD類庫也爲咱們提供了在視圖中顯示一個HUD窗口的功能。二者的基本思路是差很少的,差異更多的是在實現細節上。相對於MBProgressHUD來講,SVProgressHUD的實現有如下幾點不一樣:

  1. SVProgressHUD類對外提供的都是類方法,包括顯示、隱藏、和視圖屬性設置都是使用類方法來操做。其內部實現爲一個單例對象,類方法實際是針對這個單例對象來操做的。

  2. SVProgressHUD主要包含三部分:loading視圖、提示文本框和背景框,沒有詳情文本框。

  3. SVProgressHUD默認提供了正確、錯誤和信息三種狀態視圖(與loading視圖同一位置,根據須要來設置)。固然MBProgressHUD中,也能夠自定義視圖(customView)來顯示相應的狀態視圖。

  4. SVProgressHUD爲咱們提供了更多的交互操做,包括點擊事件、顯示事件及隱藏事件。不過這些都是經過通知的形式向外發送,因此咱們須要本身去監聽這些事件。

  5. SVProgressHUD中一些loading動畫是以Layer動畫的形式來實現的。

SVProgressHUD的實現細節還未詳細去看,有興趣的讀者能夠去研究一下。這兩個HUD類庫各有優勢,你們在使用時,可根據本身的須要和喜愛來選擇。

小結

整體來講,MBProgressHUD的代碼相對樸實,簡單易懂,沒有什麼花哨難懂的東西。就技術點而言,也沒有太多複雜的技術,都是咱們經常使用的一些東西。就使用而言,也是挺方便的,參考一下github上的使用指南就能很快上手。

相關文章
相關標籤/搜索