MBProgressHUD詳解(二)

MBProgressHUD詳解(二)

外觀

先來一發圖片,MBProgressHUD整體布局如上圖,圖片手工畫的比較丑,將就著看吧~~~


1.backgroundView 整個背景圖層,可以通過MBBackgroundView的style屬性設置樣式。跟系統有關
2.bezel視圖,提供indicator、label、detailLabel、button的背景,用來突出顯示 這個可以通過animationType屬性設置動畫效果,其實也是可選的,當mode值為MBProgressHUDModeText時,只有文本提示
3.indicator控件,指示器顯示進度情況 這個視圖由我們設定的mode屬性決定,可以是菊花、進度條,也可以是我們自定義的視圖
4.label控件,顯示簡要提示 (可選)
5.detailLabel控件,顯示詳細提示 (可選)
6.button按鈕控件,提供中間控制動作,注意:button這個按鈕只有在添加響應事件時才顯示 (可選)
style、mode、animationType可以看MBProgressHUD.h文件中的枚舉,在<strong>MBProgressHUD詳解(一)</strong>中介紹
</br>

MBProgressHUD對象的繪制

我們通過頭文件可以看到,MBProgressHUD提供了三個類函數

//創建一個新的HUD,并把它顯示在view之上,還可以設置是否以動畫的形式,此外,該函數返回一個HUD的對象
//默認removeFromSuperViewOnHide屬性為YES
+ (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated;

//找到最上層的HUD subview 并把它隱藏,成功為YES、其他情況為NO
//同時置removeFromSuperViewOnHide = YES
+ (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated;

//返回最上層的HUD subview
+ (nullable MBProgressHUD *)HUDForView:(UIView *)view;

常用的也就第一個函數+ (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated;推薦使用,這也是github中實例中使用的
</br>
此外也提供了幾個是實例函數

//以view為基準創建初始化一個HUD對象,為HUD的初始化構造函數
- (instancetype)initWithView:(UIView *)view;

//顯示HUD控件,此函數應該在主線程中調用
- (void)showAnimated:(BOOL)animated;

//隱藏HUD控件,animated控制是否顯示動畫。對應于- (void)showAnimated:(BOOL)animated;
- (void)hideAnimated:(BOOL)animated;

//在delay時間之后隱藏HUD,animated控制顯示動畫與否,delay控制延遲時間
- (void)hideAnimated:(BOOL)animated afterDelay:(NSTimeInterval)delay;

比較常用的有兩個個函數- (void)hideAnimated:(BOOL)animated;- (void)hideAnimated:(BOOL)animated afterDelay:(NSTimeInterval)delay;

接下來我們就根據程序的執行過程來一步一步分析一下代碼

初始化

+ (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated {
    MBProgressHUD *hud = [[self alloc] initWithView:view]; //創建并初始化MBProgressHUD對象
    hud.removeFromSuperViewOnHide = YES; //設置removeFromSuperViewOnHide屬性
    [view addSubview:hud];
    [hud showAnimated:animated]; //添加到父View中,并顯示
    return hud;   //返回自身
}

這個函數主要調用了兩個方法- (id)initWithView:(UIView *)view- (void)showAnimated:(BOOL)animated
函數- (id)initWithView:(UIView *)view最終調用- (void)commonInit初始化設置一些屬性

- (void)commonInit {
    //設置默認屬性
    // Set default values for properties
    _animationType = MBProgressHUDAnimationFade;
    _mode = MBProgressHUDModeIndeterminate;
    _margin = 20.0f;
    _opacity = 1.f;
    _defaultMotionEffectsEnabled = YES;

    // Default color, depending on the current iOS version
    BOOL isLegacy = kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_7_0;
    _contentColor = isLegacy ? [UIColor whiteColor] : [UIColor colorWithWhite:0.f alpha:0.7f];
    // Transparent background
    self.opaque = NO;
    self.backgroundColor = [UIColor clearColor];
    // Make it invisible for now
    self.alpha = 0.0f;
    self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    self.layer.allowsGroupOpacity = NO;

    [self setupViews]; //設置所需的子view 注意此時各個view的位置大小還未確定
    [self updateIndicators];  //設置指示器樣式
    [self registerForNotifications]; //注冊系統通知
}

這個函數里面又調用了三個函數setupViewsupdateIndicatorsregisterForNotifications,這三個函數的主要作用上面代碼注釋都說明了。特別注意的是setupViews函數返回時,各個view的位置大小還未確定。這里我們主要介紹前面兩個函數setupViews和updateIndicators,上代碼,基本的地方都有注釋

- (void)setupViews {
    UIColor *defaultColor = self.contentColor;

    //創建背景視圖
    MBBackgroundView *backgroundView = [[MBBackgroundView alloc] initWithFrame:self.bounds];
    backgroundView.style = MBProgressHUDBackgroundStyleSolidColor;
    backgroundView.backgroundColor = [UIColor clearColor];
    //自動調整view的寬度,保證左邊距和右邊距不變 | 自動調整view的高度,以保證上邊距和下邊距不變
    backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    backgroundView.alpha = 0.f;
    [self addSubview:backgroundView];
    _backgroundView = backgroundView;
    
    //創建小方塊背景視圖
    MBBackgroundView *bezelView = [MBBackgroundView new];
    //代碼層面使用Autolayout,需要對使用的View的translatesAutoresizingMaskIntoConstraints的屬性設置為NO
    bezelView.translatesAutoresizingMaskIntoConstraints = NO;
    bezelView.layer.cornerRadius = 5.f;
    bezelView.alpha = 0.f;
    [self addSubview:bezelView];
    _bezelView = bezelView;
    [self updateBezelMotionEffects]; //設置視差效果

    //創建label信息標簽,提示簡要信息
    UILabel *label = [UILabel new];
    //取消文字大小自適應
    label.adjustsFontSizeToFitWidth = NO;
    label.textAlignment = NSTextAlignmentCenter;
    label.textColor = defaultColor;
    label.font = [UIFont boldSystemFontOfSize:MBDefaultLabelFontSize];
    //告訴系統渲染器view是否不透明,設置YES可以加快渲染,默認為YES,如果設置了alpha值,應該設置為NO
    label.opaque = NO;
    label.backgroundColor = [UIColor clearColor];
    _label = label;

    //創建detailsLabel信息標簽,提示詳細信息
    UILabel *detailsLabel = [UILabel new];
    //取消文字大小自適應
    detailsLabel.adjustsFontSizeToFitWidth = NO;
    detailsLabel.textAlignment = NSTextAlignmentCenter;
    detailsLabel.textColor = defaultColor;
    detailsLabel.numberOfLines = 0;
    detailsLabel.font = [UIFont boldSystemFontOfSize:MBDefaultDetailsLabelFontSize];
    //告訴系統渲染器view是否不透明,設置YES可以加快渲染,默認為YES,如果設置了alpha值,應該設置為NO
    detailsLabel.opaque = NO;
    detailsLabel.backgroundColor = [UIColor clearColor];
    _detailsLabel = detailsLabel;

    //創建事件響應按鈕
    UIButton *button = [MBProgressHUDRoundedButton buttonWithType:UIButtonTypeCustom];
    button.titleLabel.textAlignment = NSTextAlignmentCenter;
    button.titleLabel.font = [UIFont boldSystemFontOfSize:MBDefaultDetailsLabelFontSize];
    [button setTitleColor:defaultColor forState:UIControlStateNormal];
    _button = button;
    
    //將label detailLabel button添加到蒙版視圖
    for (UIView *view in @[label, detailsLabel, button]) {
        //View的translatesAutoresizingMaskIntoConstraints的屬性設置為NO,以使用Autolayout
        view.translatesAutoresizingMaskIntoConstraints = NO;
        //當試圖變化時,設置水平和垂直方向變化的優先權
        //這是設置每一個view的優先權都是998,對自動布局不熟。。不知有何用。。尷尬
        [view setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisHorizontal];
        [view setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisVertical];
        [bezelView addSubview:view];
    }

    //頂部視圖
    UIView *topSpacer = [UIView new];
    topSpacer.translatesAutoresizingMaskIntoConstraints = NO;
    //默認隱藏
    topSpacer.hidden = YES;
    [bezelView addSubview:topSpacer];
    _topSpacer = topSpacer;

    //底部視圖
    UIView *bottomSpacer = [UIView new];
    bottomSpacer.translatesAutoresizingMaskIntoConstraints = NO;
    //默認隱藏
    bottomSpacer.hidden = YES;
    [bezelView addSubview:bottomSpacer];
    _bottomSpacer = bottomSpacer;
}

由代碼我們可以看出,這個函數首先創建了backgroundView、bezelView、label、detailsLabel、button,中間使用了一個for循環把label、detailsLabel、button添加到bezelView視圖中,最后還創建了頂部視圖和底部視圖,不過默認是隱藏的。
最初看這里的時候有個小疑惑,這里明明創建了button但是如果沒有設置button屬性,這個按鈕是不會顯示的。原來這里重新寫了一個UIbutton的子類MBProgressHUDRoundedButton,這個子類重寫了一個函數- (CGSize)intrinsicContentSize,這個函數也就是控件的內置大小。比如UILabel,UIButton等控件,他們都有自己的內置大小。我們可以重寫這個函數設置控件的大小。。

- (CGSize)intrinsicContentSize {
    // Only show, if we have associated control events.
    //allContorlEvents 獲取所有的事件集合
    //只有當有事件才顯示
    if (self.allControlEvents == 0) return CGSizeZero;
    CGSize size = [super intrinsicContentSize];
    // Add some side padding.
    size.width += 20.f;
    return size;
}

我們可以看到,如果這個button沒有任何事件的話,它的大小就是CGSizeZero(沒有大小)。
接下來我們看一下另一個函數

- (void)updateIndicators {
    UIView *indicator = self.indicator;
    //判斷目前的指示器是否為UIActivityIndicatorView
    BOOL isActivityIndicator = [indicator isKindOfClass:[UIActivityIndicatorView class]];
    //判斷目前的指示器是否為UIActivityIndicatorView
    BOOL isRoundIndicator = [indicator isKindOfClass:[MBRoundProgressView class]];

    MBProgressHUDMode mode = self.mode;
    if (mode == MBProgressHUDModeIndeterminate) { //系統自帶的指示器
        if (!isActivityIndicator) { //如果目前指示器不是UIActivityIndicatorView,則移除之前的indicator創建新的
            // 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;
    }
    //View的translatesAutoresizingMaskIntoConstraints的屬性設置為NO,以使用Autolayout
    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]; //更新布局,系統自動調用updateConstraints
}

這個函數主要是用來設置indicator指示器,根據mode的屬性顯示不同的形式,具體的可以參看代碼注釋,系統提供的菊花形狀的指示器我們就不過多說明了,一會我們著重介紹一下MBProgressHUDModeDeterminateHorizontalBar模式。
再次回到這個函數中,這個函數最后調用的是setNeedsUpdateConstraints函數,這個函數會自動調用updateConstraints,這個函數主要作用是更新一下各個控件的布局,代碼如下:

//系統自動調用
- (void)updateConstraints {
    UIView *bezel = self.bezelView;
    UIView *topSpacer = self.topSpacer;
    UIView *bottomSpacer = self.bottomSpacer;
    CGFloat margin = self.margin;
    NSMutableArray *bezelConstraints = [NSMutableArray array];
    NSDictionary *metrics = @{@"margin": @(margin)};

    NSMutableArray *subviews = [NSMutableArray arrayWithObjects:self.topSpacer, self.label, self.detailsLabel, self.button, self.bottomSpacer, nil];
    //insertObject:atIndex是插入到指定 索引的前面,即插入到數組subviews中self.label元素的前面
    if (self.indicator) [subviews insertObject:self.indicator atIndex:1];

    // Remove existing constraintes
    //移除所有約束
    [self removeConstraints:self.constraints];
    [topSpacer removeConstraints:topSpacer.constraints];
    [bottomSpacer removeConstraints:bottomSpacer.constraints];
    if (self.bezelConstraints) {
        [bezel removeConstraints:self.bezelConstraints];
        self.bezelConstraints = nil;
    }

    // Center bezel in container (self), applying the offset if set
    //將bezel View居中顯示,如果設置了偏移offset,則同時設置偏移
    CGPoint offset = self.offset;
    NSMutableArray *centeringConstraints = [NSMutableArray array];
    [centeringConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1.f constant:offset.x]];
    [centeringConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.f constant:offset.y]];
    [self applyPriority:998.f toConstraints:centeringConstraints];
    [self addConstraints:centeringConstraints];

    // Ensure minimum side margin is kept
    //與邊界保持最小間隔
    NSMutableArray *sideConstraints = [NSMutableArray array];
    [sideConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"|-(>=margin)-[bezel]-(>=margin)-|" options:0 metrics:metrics views:NSDictionaryOfVariableBindings(bezel)]];
    [sideConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(>=margin)-[bezel]-(>=margin)-|" options:0 metrics:metrics views:NSDictionaryOfVariableBindings(bezel)]];
    [self applyPriority:999.f toConstraints:sideConstraints];
    [self addConstraints:sideConstraints];

    // Minimum bezel size, if set
    //如果定義了最小的寬高,這設置其最小大小
    CGSize minimumSize = self.minSize;
    if (!CGSizeEqualToSize(minimumSize, CGSizeZero)) {
        NSMutableArray *minSizeConstraints = [NSMutableArray array];
        [minSizeConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:minimumSize.width]];
        [minSizeConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:minimumSize.height]];
        [self applyPriority:997.f toConstraints:minSizeConstraints];
        [bezelConstraints addObjectsFromArray:minSizeConstraints];
    }

    // Square aspect ratio, if set
    //強制寬高相等
    if (self.square) {
        NSLayoutConstraint *square = [NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeWidth multiplier:1.f constant:0];
        square.priority = 997.f;
        [bezelConstraints addObject:square];
    }

    //top和bottom設置
    // Top and bottom spacing
    [topSpacer addConstraint:[NSLayoutConstraint constraintWithItem:topSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:margin]];
    [bottomSpacer addConstraint:[NSLayoutConstraint constraintWithItem:bottomSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:margin]];
    // Top and bottom spaces should be equal
    [bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:topSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:bottomSpacer attribute:NSLayoutAttributeHeight multiplier:1.f constant:0.f]];

    // Layout subviews in bezel
    //bezel里面的子視圖大小設置
    NSMutableArray *paddingConstraints = [NSMutableArray new];
    [subviews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) {
        // Center in bezel
        [bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeCenterX multiplier:1.f constant:0.f]];
        // Ensure the minimum edge margin is kept
        [bezelConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"|-(>=margin)-[view]-(>=margin)-|" options:0 metrics:metrics views:NSDictionaryOfVariableBindings(view)]];
        // Element spacing
        if (idx == 0) {
            // First, ensure spacing to bezel edge
            [bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeTop multiplier:1.f constant:0.f]];
        } else if (idx == subviews.count - 1) {
            // Last, ensure spacigin to bezel edge
            [bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeBottom multiplier:1.f constant:0.f]];
        }
        if (idx > 0) {
            // Has previous
            NSLayoutConstraint *padding = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:subviews[idx - 1] attribute:NSLayoutAttributeBottom multiplier:1.f constant:0.f];
            [bezelConstraints addObject:padding];
            [paddingConstraints addObject:padding];
        }
    }];

    [bezel addConstraints:bezelConstraints];
    self.bezelConstraints = bezelConstraints;
    
    self.paddingConstraints = [paddingConstraints copy];
    [self updatePaddingConstraints];
    
    [super updateConstraints];
}

這里面用到了自動布局AutoLayout的技術,如果需要深入了解的可以自行查閱文檔。。
至此,PUD對象的創建工作就完成,現在我們來看一下指示器的幾種形式,通過代碼可知,PUD提供了幾種指示器的形式菊花、棒狀進度條,圓餅/圓環進度條。在這里我們著重介紹一下棒狀進度條。

棒狀進度條是MBBarProgressView這個類實現的,通過- (void)drawRect:(CGRect)rect這個函數繪制。

//設置棒狀進度條背景
- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    CGContextSetLineWidth(context, 2);
    CGContextSetStrokeColorWithColor(context,[_lineColor CGColor]);
    //CGContextSetFillColorWithColor(context, [_progressRemainingColor CGColor]);
    CGContextSetFillColorWithColor(context, [[UIColor redColor] CGColor]);
    
    // Draw background
    //季度條背景
    CGFloat radius = (rect.size.height / 2) - 2;
    CGContextMoveToPoint(context, 2, rect.size.height/2);
    CGContextAddArcToPoint(context, 2, 2, radius + 2, 2, radius);
    CGContextAddLineToPoint(context, rect.size.width - radius - 2, 2);
    CGContextAddArcToPoint(context, rect.size.width - 2, 2, rect.size.width - 2, rect.size.height / 2, radius);
    CGContextAddArcToPoint(context, rect.size.width - 2, rect.size.height - 2, rect.size.width - radius - 2, rect.size.height - 2, radius);
    CGContextAddLineToPoint(context, radius + 2, rect.size.height - 2);
    CGContextAddArcToPoint(context, 2, rect.size.height - 2, 2, rect.size.height/2, radius);
    CGContextFillPath(context);
    
    // Draw border
    // 進度度條移動中心線
    CGContextMoveToPoint(context, 2, rect.size.height/2);
    CGContextAddArcToPoint(context, 2, 2, radius + 2, 2, radius);
    CGContextAddLineToPoint(context, rect.size.width - radius - 2, 2);
    CGContextAddArcToPoint(context, rect.size.width - 2, 2, rect.size.width - 2, rect.size.height / 2, radius);
    CGContextAddArcToPoint(context, rect.size.width - 2, rect.size.height - 2, rect.size.width - radius - 2, rect.size.height - 2, radius);
    CGContextAddLineToPoint(context, radius + 2, rect.size.height - 2);
    CGContextAddArcToPoint(context, 2, rect.size.height - 2, 2, rect.size.height/2, radius);
    CGContextStrokePath(context);
    
    CGContextSetFillColorWithColor(context, [_progressColor CGColor]);
    radius = radius - 2;
    CGFloat amount = self.progress * rect.size.width;
    
    // Progress in the middle area
    // 設置進度條根據progress移動變長效果
    if (amount >= radius + 4 && amount <= (rect.size.width - radius - 4)) {
        CGContextMoveToPoint(context, 4, rect.size.height/2);
        CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius);
        CGContextAddLineToPoint(context, amount, 4);
        CGContextAddLineToPoint(context, amount, radius + 4);
        
        CGContextMoveToPoint(context, 4, rect.size.height/2);
        CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius);
        CGContextAddLineToPoint(context, amount, rect.size.height - 4);
        CGContextAddLineToPoint(context, amount, radius + 4);
        
        CGContextFillPath(context);
    }
    
    // Progress in the right arc
    //右邊界圓角效果
    else if (amount > radius + 4) {
        CGFloat x = amount - (rect.size.width - radius - 4);

        CGContextMoveToPoint(context, 4, rect.size.height/2);
        CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius);
        CGContextAddLineToPoint(context, rect.size.width - radius - 4, 4);
        CGFloat angle = -acos(x/radius);
        if (isnan(angle)) angle = 0;
        CGContextAddArc(context, rect.size.width - radius - 4, rect.size.height/2, radius, M_PI, angle, 0);
        CGContextAddLineToPoint(context, amount, rect.size.height/2);

        CGContextMoveToPoint(context, 4, rect.size.height/2);
        CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius);
        CGContextAddLineToPoint(context, rect.size.width - radius - 4, rect.size.height - 4);
        angle = acos(x/radius);
        if (isnan(angle)) angle = 0;
        CGContextAddArc(context, rect.size.width - radius - 4, rect.size.height/2, radius, -M_PI, angle, 1);
        CGContextAddLineToPoint(context, amount, rect.size.height/2);
        
        CGContextFillPath(context);
    }
    
    // Progress is in the left arc
    // 左邊界圓角效果
    else if (amount < radius + 4 && amount > 0) {
        CGContextMoveToPoint(context, 4, rect.size.height/2);
        CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius);
        CGContextAddLineToPoint(context, radius + 4, rect.size.height/2);

        CGContextMoveToPoint(context, 4, rect.size.height/2);
        CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius);
        CGContextAddLineToPoint(context, radius + 4, rect.size.height/2);
        
        CGContextFillPath(context);
    }
}

這個函數雖然很長,但是它主要繪制了兩個部分,進度條最外面的橢圓環和內部的進度條,內部的進度條根據其progress實現長短變化。
</br>

顯示

PUD對象的顯示只有一個函數- (void)showAnimated:(BOOL)animated,代碼如下:

//根據參數顯示HUD對象
- (void)showAnimated:(BOOL)animated {
    MBMainThreadAssert(); //顯示放在主線程中
    [self.minShowTimer invalidate]; //取消定時器
    self.useAnimation = animated;
    self.finished = NO;
    // If the grace time is set postpone the HUD display
    //如果設置了寬限時間graceTime,則延遲顯示,否則直接顯示
    if (self.graceTime > 0.0) {
        //創建定時器,并把它加入到NDRunLoop中
        NSTimer *timer = [NSTimer timerWithTimeInterval:self.graceTime target:self selector:@selector(handleGraceTimer:) userInfo:nil repeats:NO];
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
        self.graceTimer = timer;
    } 
    // ... otherwise show the HUD imediately 
    else {
        [self showUsingAnimation:self.useAnimation];
    }
}

這個函數有個需要注意的地方:此函數必須在主線程中執行。

消失

PUD對象提供了兩個隱藏的函數- (void)hide:(BOOL)animated- (void)hide:(BOOL)animated afterDelay:(NSTimeInterval)delay,通過名字就額可以看出第二個函數是延遲delay時間在隱藏消失,

- (void)hideAnimated:(BOOL)animated afterDelay:(NSTimeInterval)delay {
    //創建定時器,并把它加入到NDRunLoop中
    NSTimer *timer = [NSTimer timerWithTimeInterval:delay target:self selector:@selector(handleHideTimer:) userInfo:@(animated) repeats:NO];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    self.hideDelayTimer = timer;
}

只是簡單的創建一個定時器,并把定時器加入到NDRunLoop中,延遲delay執行handleHideTimer:函數。</br>
這兩個函數最后都調用函數- (void)hideAnimated:(BOOL)animated上,代碼如下:

- (void)hideAnimated:(BOOL)animated {
    MBMainThreadAssert();
    [self.graceTimer invalidate]; //時間重置
    self.useAnimation = animated;
    self.finished = YES;
    // 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 && self.showStarted) {
        NSTimeInterval interv = [[NSDate date] timeIntervalSinceDate:self.showStarted];
        if (interv < self.minShowTime) {
            //創建定時器,并把它加入到NDRunLoop中
            NSTimer *timer = [NSTimer timerWithTimeInterval:(self.minShowTime - interv) target:self selector:@selector(handleMinShowTimer:) userInfo:nil repeats:NO];
            [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
            self.minShowTimer = timer;
            return;
        } 
    }
    // ... otherwise hide the HUD immediately
    //直接隱藏
    [self hideUsingAnimation:self.useAnimation];
}

這個函數同樣設置了一個定時器,根據minShowTime屬性,控制PUD顯示的時機。
</br>
最后的最后,顯示和隱藏都統一到一個函數中- (void)animateIn:(BOOL)animatingIn withType:(MBProgressHUDAnimation)type completion:(void(^)(BOOL finished))completion,在這個函數中,我們可以設置一些PUD對象出現和隱藏時的動畫效果,具體請看代碼注釋。

//消失或出現時的伸縮效果,以及透明度
- (void)animateIn:(BOOL)animatingIn withType:(MBProgressHUDAnimation)type completion:(void(^)(BOOL finished))completion {
    // Automatically determine the correct
    if (type == MBProgressHUDAnimationZoom) {
        type = animatingIn ? MBProgressHUDAnimationZoomIn : MBProgressHUDAnimationZoomOut;
    }

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

    // Set starting state
    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;
    }

    // Perform animations
    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;
        }
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wdeprecated-declarations"
        bezelView.alpha = animatingIn ? self.opacity : 0.f;
    #pragma clang diagnostic pop
        self.backgroundView.alpha = animatingIn ? 1.f : 0.f;
    };

    // Spring animations are nicer, but only available on iOS 7+
    #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 || TARGET_OS_TV
    if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_7_0) {
        [UIView animateWithDuration:0.3 delay:0. usingSpringWithDamping:1.f initialSpringVelocity:0.f options:UIViewAnimationOptionBeginFromCurrentState animations:animations completion:completion];
        return;
    }
    #endif
    [UIView animateWithDuration:0.3 delay:0. options:UIViewAnimationOptionBeginFromCurrentState animations:animations completion:completion];
}

刪除

經過以上步驟,PUD經歷了創建、顯示和隱藏,但是對象并沒消失,只是隱藏了,變透明了。所以還需一個函數處理一下后續動作- (void)doneFinished:(BOOL)finished

//完成后清理動作
- (void)doneFinished:(BOOL)finished {
    // Cancel any scheduled hideDelayed: calls
    [self.hideDelayTimer invalidate];

    if (finished) {
        self.alpha = 0.0f;
        if (self.removeFromSuperViewOnHide) {
            //從父視圖中移除自己以及子視圖
            [self removeFromSuperview];
        }
    }

    if (self.completionBlock) {
        MBProgressHUDCompletionBlock block = self.completionBlock;
        self.completionBlock = NULL;
        block();
    }
    id<MBProgressHUDDelegate> delegate = self.delegate;
    if ([delegate respondsToSelector:@selector(hudWasHidden:)]) {
        [delegate performSelector:@selector(hudWasHidden:) withObject:self];
    }
}

這個函數如果removeFromSuperViewOnHide屬性為YES,則將自己從父視圖上移除,如果有completionBlock回調函數,則執行回調,如果實現了代理并實現了代理方法,則執行代理方法。

到這里整個的執行流程差不多就算結束了,剩下的清理工作都是系統自動調用,就不過多說明了。。

寫的比較亂,謝謝你們能夠忍著看完,如果有什么錯誤或者不恰當的地方,請不留情面的指出來,共同交流,共同進步。。。

謝謝!
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容