MBProgressHUD是一个很受欢迎的第三方库,其用法也简单,内部实现代码也简洁易懂,十分适合初学者学习。通过该开源库的学习,我们可以学到如何自定义view,以及一些写开源库的小知识点。
以下我会分成三部分来讲解:
① 简单介绍其组成和核心API
② 深入了解其内部实现
③ 值得学习的技巧及思路
① 简单介绍其组成和核心API
MBProgressHUD有五个类,都写在同一个.h、.m中。
- MBProgressHUD(核心类)
/* 暴露的API */
//将HUD添加到指定的view,并返回HUD
+ (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated;
//将HUD从指定的view移除
+ (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated;
//获取指定view上的HUD
+ (nullable MBProgressHUD *)HUDForView:(UIView *)view;
//创建在指定view上的HUD
- (instancetype)initWithView:(UIView *)view;
//显示HUD
- (void)showAnimated:(BOOL)animated;
//隐藏HUD
- (void)hideAnimated:(BOOL)animated;
//延迟隐藏HUD
- (void)hideAnimated:(BOOL)animated afterDelay:(NSTimeInterval)delay;
/* 可修改的属性 */
@property (weak, nonatomic) id<MBProgressHUDDelegate> delegate;
@property (copy, nullable) MBProgressHUDCompletionBlock completionBlock;
@property (assign, nonatomic) NSTimeInterval graceTime;//设定一个最小完成任务时间,超过该时间未完成才需要显示HUD
@property (assign, nonatomic) NSTimeInterval minShowTime;//设定一个最小展示时间,至少展示这个时间
@property (assign, nonatomic) BOOL removeFromSuperViewOnHide;
@property (assign, nonatomic) MBProgressHUDMode mode;//HUD样式
@property (assign, nonatomic) MBProgressHUDAnimation animationType;//动画类型
@property (strong, nonatomic, nullable) UIColor *contentColor;//HUD内容颜色
@property (assign, nonatomic) CGPoint offset;//HUD距离背景view中心的offset
@property (assign, nonatomic) CGFloat margin;//content距离HUD边的margin
@property (assign, nonatomic) CGSize minSize;//HUD的最小size
@property (assign, nonatomic, getter = isSquare) BOOL square;//HUD是否正方形
@property (assign, nonatomic, getter=areDefaultMotionEffectsEnabled) BOOL defaultMotionEffectsEnabled;//是否支持背景视差效果
@property (assign, nonatomic) float progress;//进度条进度
@property (strong, nonatomic, nullable) NSProgress *progressObject;//进度条,不过实际使用到的还是自制的
// 以下主要用于设置内容
@property (strong, nonatomic, readonly) MBBackgroundView *bezelView;//HUDView
@property (strong, nonatomic, readonly) MBBackgroundView *backgroundView;//HUD背景View
@property (strong, nonatomic, nullable) UIView *customView;//自定义View,在label上方,用于放置图片或进度器
@property (strong, nonatomic, readonly) UILabel *label;
@property (strong, nonatomic, readonly) UILabel *detailsLabel;
@property (strong, nonatomic, readonly) UIButton *button;
- MBBarProgressView(条状进度器)
- MBBackgroundView (背景View,带有高斯模糊)
- MBRoundProgressView (圆形进度器)
- MBProgressHUDRoundedButton (带圆角的按钮)
② 深入了解其内部实现
要看内部实现我们又分成三部分(初始化、显示、隐藏):
- HUD的初始化
我们调用showHUDAddedTo:animated:创建HUD,并显示。
+ (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated {
MBProgressHUD *hud = [[self alloc] initWithView:view];//初始化HUD
hud.removeFromSuperViewOnHide = YES;
[view addSubview:hud];//添加到view上
[hud showAnimated:animated];//显示
return hud;
}
我们先来看HUD如何初始化的,这边实现了initWithFrame:和initWithCoder:,并在方法里面进行数据的初始化。这样无论是使用代码还是可视化都可以用该框架。
//用于纯代码
- (instancetype)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
[self commonInit];
self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.3];
}
return self;
}
//用于可视化(nib)
- (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方法中除了设置默认值、默认属性,主要还设置了HUD里面的子视图(进度器,提示文字等)
- (void)commonInit {
// 设置默认值、默认属性
_animationType = MBProgressHUDAnimationFade;
_mode = MBProgressHUDModeIndeterminate;
_margin = 20.0f;
_opacity = 1.f;
_defaultMotionEffectsEnabled = YES;
...(此处省略很多代码)
[self setupViews];//初始化子视图
[self updateIndicators];//根据进度器模式选择进度器,并且设置颜色
[self registerForNotifications];//注册通知,屏幕旋转时做视图的旋转
}
从这里可以我们可以看出HUD的组成,到这一步就能确定下子视图的内容, 并且设置setNeedsUpdateConstraints。
在updateConstraints方法中添加约束,这边用的是AutoLayout,不清楚的可以看看这篇文章:NSLayoutConstraint-代码实现自动布局。
通过AutoLayout,某个视图不显示时也能设置好间距,达到灵活布局的作用。
- HUD的显示
这边有一点需要注意的是,有个graceTime(优化时间),比如当请求任务很快时,HUD就会一闪而过。所以为防止这种现象,设置一个graceTime,只有当超过该时间未完成才会执行显示HUD的任务。
- (void)showAnimated:(BOOL)animated {
MBMainThreadAssert();
[self.minShowTimer invalidate];
self.useAnimation = animated;
self.finished = NO;
//设定一个最小完成任务时间,超过该时间未完成才会执行显示HUD的任务
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)showUsingAnimation:(BOOL)animated {
//取消之前的动画
[self.bezelView.layer removeAllAnimations];
[self.backgroundView.layer removeAllAnimations];
// Cancel any scheduled hideDelayed: calls
[self.hideDelayTimer invalidate];
//记录开始时间
self.showStarted = [NSDate date];
self.alpha = 1.f;
// 进度器模式需要创建CADisplayLink
[self setNSProgressDisplayLinkEnabled:YES];
if (animated) {
//执行动画
[self animateIn:YES withType:self.animationType completion:NULL];
} else {
//直接显示
self.bezelView.alpha = 1.f;
self.backgroundView.alpha = 1.f;
}
}
- HUD的隐藏
通过hideHUDForView:获取view上所有的view执行隐藏任务。
+ (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;
}
隐藏有两种方式,一种是执行调用隐藏,一种是延迟后隐藏(也是利用定时器),不过最终都是调用hideAnimated:。在hideAnimated:方法中,有个minShowTime(minShowTime),比如当HUD刚展示出来,任务刚好完成,HUD就会一闪而过。所以为防止这种现象,设置一个设置最小展示时间minShowTime,就要先展示够时间再执行隐藏任务。
- (void)hideAnimated:(BOOL)animated {
MBMainThreadAssert();
[self.graceTimer invalidate];
self.useAnimation = animated;
self.finished = YES;
// 如果有设置最小展示时间minShowTime,就要先展示够时间再执行隐藏任务
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];//状态的重置和定时器的清空,block的回调
}];
} else {//无动画
self.showStarted = nil;
self.bezelView.alpha = 0.f;
self.backgroundView.alpha = 1.f;
[self done];//状态的重置和定时器的清空,block的回调
}
}
- (void)done {
// Cancel any scheduled hideDelayed: calls
[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源码的解读,可以学到API接口的设计,控件的封装,同时也可以掌握自定义控件的一些细节,比如:
- AutoLayout布局(当我们自定义控件时,肯定要减少其他第三方的引入,所以布局方式上就不能直接用masony)。
//设置固有size
- (CGSize)intrinsicContentSize;
//更新约束
- (void)updateConstraints;
- 细节上的优化,比如graceTime和minShowTime(使用NSTimer来实现),以及使用CADisplayLink来处理刷新频率高的事件。
_progressObjectDisplayLink = [CADisplayLink displayLinkWithTarget:self
[_progressObjectDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
selector:@selector(updateProgressFromProgressObject)];
- 宏定义和静态常量的使用。
#define MBMainThreadAssert() NSAssert([NSThread isMainThread], @"MBProgressHUD needs to be accessed on the main thread.");
CGFloat const MBProgressMaxOffset = 1000000.f;
static const CGFloat MBDefaultPadding = 4.f;
- 一些属性设置上的小细节(先判断是否相等,最后看是否更新display还是Constraints)。
- (void)setProgress:(float)progress {
if (progress != _progress) {
_progress = progress;
[self setNeedsDisplay];
}
}
- (void)setMargin:(CGFloat)margin {
if (margin != _margin) {
_margin = margin;
[self setNeedsUpdateConstraints];
}
}
- 在drawRect:方法中绘制自定义图形。
- UIBlurEffect(高斯模糊)、UIInterpolatingMotionEffect(背景视差效果)、屏幕横竖屏切换的处理。
网友评论