MBProgressHUD是一个优秀的弹窗提示的一个开源框架,项目中几乎都会用到。
一、先看一下该弹窗的显示模式:
typedef NS_ENUM(NSInteger, MBProgressHUDMode) {
/// 默认模式,使用系统自带的菊花
MBProgressHUDModeIndeterminate,
/// 带饼图进度条
MBProgressHUDModeDeterminate,
/// 带条形进度条
MBProgressHUDModeDeterminateHorizontalBar,
/// 圆环进度条
MBProgressHUDModeAnnularDeterminate,
/// 自定义
MBProgressHUDModeCustomView,
/// 纯文本
MBProgressHUDModeText
};
1、带加载菊花的弹窗
2、带菊花和文字的弹窗
3、带进度条和文字的弹窗
4、带进度条文字和取消按钮的弹窗
5、带条形进度条和文字的弹窗
6、普通的toast弹窗
7、带文字和图片的弹窗
以上样式几乎可以适用大部分的应用场景了,除此以外MBProgressHUD还支持自定义样式
二、用法
+ (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated;
+ (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated;
- (instancetype)initWithView:(UIView *)view;
- (void)showAnimated:(BOOL)animated;
- (void)hideAnimated:(BOOL)animated;
- (void)hideAnimated:(BOOL)animated afterDelay:(NSTimeInterval)delay;
通过头文件可以看出使用方式有两种,一种的类函数,另一种是直接init初始化调用显示
三、MBProgressHUD的层次结构
/// 最外层的半透明背景
@property (strong, nonatomic, readonly) MBBackgroundView *backgroundView;
/// 中间的圆角矩形
@property (strong, nonatomic, readonly) MBBackgroundView *bezelView;
/// 自定义View
@property (strong, nonatomic, nullable) UIView *customView;
/// 文本
@property (strong, nonatomic, readonly) UILabel *label;
/// 详情文本
@property (strong, nonatomic, readonly) UILabel *detailsLabel;
/// 按钮
@property (strong, nonatomic, readonly) UIButton *button;
四、初始化函数
- (instancetype)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
[self commonInit];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if ((self = [super initWithCoder:aDecoder])) {
[self commonInit];
}
return self;
}
- (id)initWithView:(UIView *)view {
NSAssert(view, @"View must not be nil.");
return [self initWithFrame:view.bounds];
}
由此看出所有的初始化调用最终都会走到commonInit
- (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];
}
setupViews函数这里把主要的backgroundView、bezelView初始化并且加入MBProgressHUD的子view里面,且把label、detailLabel、button都添加进bezelView里面了
- (void)setupViews {
UIColor *defaultColor = self.contentColor;
MBBackgroundView *backgroundView = [[MBBackgroundView alloc] initWithFrame:self.bounds];
…
_backgroundView = backgroundView;
MBBackgroundView *bezelView = [MBBackgroundView new];
…
_bezelView = bezelView;
[self updateBezelMotionEffects];
UILabel *label = [UILabel new];
…
_label = label;
UILabel *detailsLabel = [UILabel new];
…
_detailsLabel = detailsLabel;
UIButton *button = [MBProgressHUDRoundedButton buttonWithType:UIButtonTypeCustom];
…
_button = button;
for (UIView *view in @[label, detailsLabel, button]) {
view.translatesAutoresizingMaskIntoConstraints = NO;
[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;
}
updateIndicators根据mode创建相应的进度条,如果是MBProgressHUDModeCustomView模式则会创建自定义的View
- (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) {
// 自定义View
[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];
}
五、结束函数
结束函数最终都会走到hideAnimated函数里来,这里有个小细节,会判断显示时间如果小于设定的minShowTime时间的话就会延迟隐藏自己
- (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)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 {
[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];
}
}
小细节:
1、MBMainThreadAssert(),断言当前是否在主线程执行,宏定义如下
#define MBMainThreadAssert() NSAssert([NSThread isMainThread], @"MBProgressHUD needs to be accessed on the main thread.");
2、定义常量
extern CGFloat const MBProgressMaxOffset;
CGFloat const MBProgressMaxOffset = 1000000.f;