在项目中经常会使用MBProgressHUD
来实现弹窗提醒,所有来分析下MBProgressHUD
这个三方库的代码。所分析的源码版本号为1.0.0。
这篇总结主要分三个部分来介绍分析这个框架:
代码结构
类图
核心API
属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
@property (assign, nonatomic) NSTimeInterval graceTime;
@property (assign, nonatomic) NSTimeInterval minShowTime;
@property (assign, nonatomic) BOOL removeFromSuperViewOnHide;
@property (assign, nonatomic) MBProgressHUDMode mode;
|
类方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
+ (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated;
+ (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated;
+ (nullable MBProgressHUD *)HUDForView:(UIView *)view;
|
实例方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
- (instancetype)initWithView:(UIView *)view;
- (void)showAnimated:(BOOL)animated;
- (void)hideAnimated:(BOOL)animated;
|
方法调用流程图
从MBProgressHUD
提供的主要接口可以看出,主要有显示HUD和隐藏HUD这两个功能,一步步追溯,得出的方法调用流程图如下:
方法内部实现
方法的内部实现主要从两个方面来分析,显示HUD和隐藏HUD。
显示HUD
首先是MBProgressHUD的构造方法
1 2 3 4 5 6 7 8 9
| + (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated { MBProgressHUD *hud = [[self alloc] initWithView:view]; hud.removeFromSuperViewOnHide = YES; [view addSubview:hud]; [hud showAnimated:animated]; [[UINavigationBar appearance] setBarTintColor:nil]; return hud; }
|
首先进入- (id)initWithView:(UIView *)view
方法,再进入- (instancetype)initWithFrame:(CGRect)frame
方法,最后调用- (void)commonInit
方法,进行属性的初始化和添加子视图。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| - (void)commonInit { _animationType = MBProgressHUDAnimationFade; _mode = MBProgressHUDModeIndeterminate; _margin = 20.0f; _opacity = 1.f; _defaultMotionEffectsEnabled = YES;
BOOL isLegacy = kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_7_0; _contentColor = isLegacy ? [UIColor whiteColor] : [UIColor colorWithWhite:0.f alpha:0.7f]; self.opaque = NO; self.backgroundColor = [UIColor clearColor]; self.alpha = 0.0f; self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; self.layer.allowsGroupOpacity = NO; [self setupViews]; [self updateIndicators]; [self registerForNotifications]; }
|
添加子视图都是常见的方式,让视图跟随陀螺仪运动,这个之前没有接触过,后续需要了解下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| - (void)updateBezelMotionEffects { #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 || TARGET_OS_TV MBBackgroundView *bezelView = self.bezelView; if (![bezelView respondsToSelector:@selector(addMotionEffect:)]) return;
if (self.defaultMotionEffectsEnabled) { CGFloat effectOffset = 10.f; UIInterpolatingMotionEffect *effectX = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.x" type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis]; effectX.maximumRelativeValue = @(effectOffset); effectX.minimumRelativeValue = @(-effectOffset);
UIInterpolatingMotionEffect *effectY = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.y" type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis]; effectY.maximumRelativeValue = @(effectOffset); effectY.minimumRelativeValue = @(-effectOffset);
UIMotionEffectGroup *group = [[UIMotionEffectGroup alloc] init]; group.motionEffects = @[effectX, effectY];
[bezelView addMotionEffect:group]; } else { NSArray *effects = [bezelView motionEffects]; for (UIMotionEffect *effect in effects) { [bezelView removeMotionEffect:effect]; } } #endif }
|
再主要看下更新指示器的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| - (void)updateIndicators { UIView *indicator = self.indicator; BOOL isActivityIndicator = [indicator isKindOfClass:[UIActivityIndicatorView class]]; BOOL isRoundIndicator = [indicator isKindOfClass:[MBRoundProgressView class]];
MBProgressHUDMode mode = self.mode; if (mode == MBProgressHUDModeIndeterminate) { if (!isActivityIndicator) { [indicator removeFromSuperview]; indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; [(UIActivityIndicatorView *)indicator startAnimating]; [self.bezelView addSubview:indicator]; } } else if (mode == MBProgressHUDModeDeterminateHorizontalBar) { [indicator removeFromSuperview]; indicator = [[MBBarProgressView alloc] init]; [self.bezelView addSubview:indicator]; } else if (mode == MBProgressHUDModeDeterminate || mode == MBProgressHUDModeAnnularDeterminate) { if (!isRoundIndicator) { [indicator removeFromSuperview]; indicator = [[MBRoundProgressView alloc] init]; [self.bezelView addSubview:indicator]; } if (mode == MBProgressHUDModeAnnularDeterminate) { [(MBRoundProgressView *)indicator setAnnular:YES]; } } else if (mode == MBProgressHUDModeCustomView && self.customView != indicator) { [indicator removeFromSuperview]; indicator = self.customView; [self.bezelView addSubview:indicator]; } else if (mode == MBProgressHUDModeText) { [indicator removeFromSuperview]; indicator = nil; } 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]; }
|
在这个方法中,主要是根据显示的模式,将不同的indicator
视图赋值给indicator
属性。更新完指示器后,就是开始将视图显示在界面上。调用的是- (void)showAnimated:(BOOL)animated
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| - (void)showAnimated:(BOOL)animated { MBMainThreadAssert(); [self.minShowTimer invalidate]; self.useAnimation = animated; self.finished = NO; 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; } else { [self showUsingAnimation:self.useAnimation]; } }
|
在- (void)showAnimated:(BOOL)animated
方法中,主要做的是判断是否设置了推迟显示HUD的时间,如果设置了,就推迟设置的时间再显示。最后,执行- (void)showUsingAnimation:(BOOL)animated
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| - (void)showUsingAnimation:(BOOL)animated { [self.bezelView.layer removeAllAnimations]; [self.backgroundView.layer removeAllAnimations];
[self.hideDelayTimer invalidate]; self.showStarted = [NSDate date]; self.alpha = 1.f;
[self setNSProgressDisplayLinkEnabled:YES];
if (animated) { [self animateIn:YES withType:self.animationType completion:NULL]; } else { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" self.bezelView.alpha = self.opacity; #pragma clang diagnostic pop self.backgroundView.alpha = 1.f; } }
|
最后执行- (void)animateIn:(BOOL)animatingIn withType:(MBProgressHUDAnimation)type completion:(void(^)(BOOL finished))completion
方法,这个方法显示和隐藏均会调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| - (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);
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; }
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; };
#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]; }
|
从代码可以看出,这里只是对指示器的父视图做了放大缩小的动画。
隐藏HUD
1 2 3 4 5 6 7 8 9 10 11
| + (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated { MBProgressHUD *hud = [self HUDForView:view]; if (hud != nil) { hud.removeFromSuperViewOnHide = YES; [hud hideAnimated:animated]; return YES; } return NO; }
|
在这个方法的执行过程中,调用- (void)hideAnimated:(BOOL)animated
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| - (void)hideAnimated:(BOOL)animated { MBMainThreadAssert(); [self.graceTimer invalidate]; self.useAnimation = animated; self.finished = YES; if (self.minShowTime > 0.0 && self.showStarted) { NSTimeInterval interv = [[NSDate date] timeIntervalSinceDate:self.showStarted]; if (interv < self.minShowTime) { 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; } } [self hideUsingAnimation:self.useAnimation]; }
|
- (void)hideAnimated:(BOOL)animated
方法中,主要做的是判断是否需要推迟隐藏HUD,最后调用- (void)hideUsingAnimation:(BOOL)animated
方法,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| - (void)hideUsingAnimation:(BOOL)animated { if (animated && self.showStarted) { self.showStarted = nil; [self animateIn:NO withType:self.animationType completion:^(BOOL finished) { [self done]; }]; } else { self.showStarted = nil; self.bezelView.alpha = 0.f; self.backgroundView.alpha = 1.f; [self done]; } }
|
最后,调用- (void)done
方法。这个方法主要负责属性的释放和隐藏完成回调的处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| - (void)done { [self.hideDelayTimer invalidate]; [self setNSProgressDisplayLinkEnabled:NO];
if (self.hasFinished) { self.alpha = 0.0f; if (self.removeFromSuperViewOnHide) { [self removeFromSuperview]; } } MBProgressHUDCompletionBlock completionBlock = self.completionBlock; if (completionBlock) { completionBlock(); } id<MBProgressHUDDelegate> delegate = self.delegate; if ([delegate respondsToSelector:@selector(hudWasHidden:)]) { [delegate performSelector:@selector(hudWasHidden:) withObject:self]; } }
|
总结
从代码来看,MBProgressHUD
这个三方库有几个地方值借鉴:
graceTime
和minShowTime
,在开发的时候会出现显示HUD后,存在缓存或者网速较好时,HUD显示到HUD隐藏的时间较短,界面出现闪动的情况,这时,就可以通过设置graceTime
和minShowTime
来处理,达到更好的用户体验。 这个在封装弹窗控件时,可以参考。