MBProgressHUD
是一个显示提示窗口的三方库,常用于用户交互、后台耗时操作等的提示。通过显示一个提示框,通知用户操作或任务的执行状态;同时,利用动画效果,降低用户等待的焦虑心理,增强用户体验。
本篇文章从源码角度来看一下MBProgressHUD
是如何实现的,所用的知识都是比较基础的,不过还是值得我们学习一下。详解如下:
1. 类介绍
-
MBProgressHUD
这是MBProgressHUD
的主要类,提供丰富的属性来调整视图的样式。 -
MBRoundProgressView
这是提供Determinate
视图显示的类,有非圆环和圆环视图两种方式。 -
MBBarProgressView
这是提供进度条的视图类。 -
MBBackgroundView
这是MBProgressHUD
的背景视图类,利用UIVisualEffectView
提供毛玻璃效果
2. MBProgressHUD类的显示模式
-
MBProgressHUDModeIndeterminate
-
MBProgressHUDModeDeterminate
-
MBProgressHUDModeDeterminateHorizontalBar
-
MBProgressHUDModeAnnularDeterminate
-
MBProgressHUDModeCustomView
这是自定义视图 -
MBProgressHUDModeText
3.动画模式
-
MBProgressHUDAnimationFade
: 渐变模式 -
MBProgressHUDAnimationZoom
: Zoom In & Zoom Out -
MBProgressHUDAnimationZoomOut
: 消失时带变小动画 -
MBProgressHUDAnimationZoomIn
: 出现时带变大动画
4. 背景样式
-
MBProgressHUDBackgroundStyleSolidColor
: 正常颜色 -
MBProgressHUDBackgroundStyleBlur
: 毛玻璃效果
5. 视图内容
-
@property (strong, nonatomic, readonly) UILabel *label;
: 标题 -
@property (strong, nonatomic, readonly) UILabel *detailsLabel;
:详情 -
@property (strong, nonatomic, readonly) UIButton *button
: 按钮(显示在标题下方) -
@property (strong, nonatomic, nullable) UIView *customView;
:用户自定义视图 -
@property (strong, nonatomic, readonly) MBBackgroundView *backgroundView;
: 整个背景视图 -
@property (strong, nonatomic, readonly) MBBackgroundView *bezelView;
:提示框背景视图 -
@property (strong, nonatomic, nullable) UIColor *contentColor UI_APPEARANCE_SELECTOR;
: 提示框的内容颜色 -
@property (assign, nonatomic) CGPoint offset UI_APPEARANCE_SELECTOR;
:提示框相对父视图中心点的偏移量 -
@property (assign, nonatomic) CGFloat margin UI_APPEARANCE_SELECTOR;
:提示框内的内容视图的边距 -
@property (assign, nonatomic) CGSize minSize UI_APPEARANCE_SELECTOR;
:提示框最小尺寸 -
@property (assign, nonatomic) BOOL removeFromSuperViewOnHide;
:隐藏时从父视图中删除 -
@property (assign, nonatomic) NSTimeInterval graceTime;
:延迟多久后显示提示框,避免快速执行的任务也显示提示框,给用户造成视觉干扰。 -
@property (assign, nonatomic) NSTimeInterval minShowTime;
:提示框视图最少展示的时间
6. 创建和隐藏视图
- 创建流程
通过+ (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated
类方法创建视图,也可以通过对象方法创建,不过建议用类方法,不仅创建方便,而且会自动的添加到父视图,然后进行显示。其中,创建过程如下:
- (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];
[self updateIndicators];
[self registerForNotifications];
}
我们可以发现,通过添加子空间后,根据视图模式调用updateIndicators
方法来创建不同的视图,最后添加了一个状态栏的通知,用来在横竖屏时跳转视图。其中,在显示提示框时,会首先判断graceTime
,如过不为0
,那么就创建一个定时器倒计时,时间到之后再判断任务是否结束,如果finished
不为空,就开始显示提示框。
- (void)showAnimated:(BOOL)animated {
MBMainThreadAssert();
[self.minShowTimer invalidate];
self.useAnimation = animated;
self.finished = NO;
// If the grace time is set, postpone the HUD display
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;
}
// ... otherwise show the HUD immediately
else {
[self showUsingAnimation:self.useAnimation];
}
}
- (void)handleGraceTimer:(NSTimer *)theTimer {
// Show the HUD only if the task is still running
if (!self.hasFinished) {
[self showUsingAnimation:self.useAnimation];
}
}
- 隐藏视图
通过+ (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated
隐藏视图,其中会根据minShowTime
来判断是否立即隐藏提示框。如果,minShowTime
不为0,那么会创建一个定时器,并把定时器加入到common
模式的runloop
里,等时间到后再把提示框隐藏。
- (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 postpone the hiding operation if necessary
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;
}
}
// ... otherwise hide the HUD immediately
[self hideUsingAnimation:self.useAnimation];
}
7. MBRoundProgressView 和 MBBarProgressView
这两个类,分别创建了 Determinate
和 进度条 的提示框视图,具体实现方法是在 - (void)drawRect:(CGRect)rect
方法里通过 UIBezierPath
或者 Quarts2D
画出,设计思想算是常规,请参考代码细读。
8. MBProgressHUD应用
对于三方框架,使用之前,最好先封装一层(继承或分类),方便以后的调试和新框架替换。封装时,尽量用类方法,使用时比较简洁。
- 添加提示框
+ (void)showHUDWithText:(NSString *)text inView:(UIView *)view deley:(NSTimeInterval)time
{
if (text == nil || text.length <= 0) {
return;
}
if (view == nil) {
view = [[UIApplication sharedApplication].windows lastObject];
}
MBProgressHUD *HUD = [MBProgressHUD showHUDAddedTo:view animated:YES];
HUD.mode = MBProgressHUDModeText;
[HUD hideAnimated:YES afterDelay:1.5];
}
- 隐藏提示框 (改方法调用时,最好在主线程,异步线程可能会出现问题)
+ (void)hideHUDForView:(UIView *)view
{
if (view == nil) view = [[UIApplication sharedApplication].windows lastObject];
[self hideHUDForView:view animated:YES];
}