MBProgressHUD详解(二)

MBProgressHUD详解(二)

外观

先来一发图片,MBProgressHUD整体布局如上图,图片手工画的比较丑,将就着看吧~~~

MBProgressHUD详解(二)_第1张图片

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文件中的枚举,在MBProgressHUD详解(一)中介绍

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中实例中使用的


此外也提供了几个是实例函数

//以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提供了几种指示器的形式菊花、棒状进度条,圆饼/圆环进度条。在这里我们着重介绍一下棒状进度条。

MBProgressHUD详解(二)_第2张图片

棒状进度条是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实现长短变化。

显示

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:函数。

这两个函数最后都调用函数- (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显示的时机。


最后的最后,显示和隐藏都统一到一个函数中- (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 delegate = self.delegate;
    if ([delegate respondsToSelector:@selector(hudWasHidden:)]) {
        [delegate performSelector:@selector(hudWasHidden:) withObject:self];
    }
}

这个函数如果removeFromSuperViewOnHide属性为YES,则将自己从父视图上移除,如果有completionBlock回调函数,则执行回调,如果实现了代理并实现了代理方法,则执行代理方法。

到这里整个的执行流程差不多就算结束了,剩下的清理工作都是系统自动调用,就不过多说明了。。

写的比较乱,谢谢你们能够忍着看完,如果有什么错误或者不恰当的地方,请不留情面的指出来,共同交流,共同进步。。。

谢谢!

你可能感兴趣的:(MBProgressHUD详解(二))