在項目中經(jīng)常會使用MBProgressHUD
來實現(xiàn)彈窗提醒,所有來分析下MBProgressHUD
這個三方庫的代碼。所分析的源碼版本號為1.0.0擒抛。
這篇總結(jié)主要分三個部分來介紹分析這個框架:
- 代碼結(jié)構(gòu)
- 方法調(diào)用流程圖
- 方法內(nèi)部實現(xiàn)
代碼結(jié)構(gòu)
類圖
核心API
屬性
/*
* 用來推遲HUD的顯示,避免HUD顯示時間過短,出現(xiàn)一閃而逝的情況拌蜘,默認值為0。
*/
@property (assign, nonatomic) NSTimeInterval graceTime;
/**
* HUD最短顯示時間牙丽,單位為s简卧,默認值為0。
*/
@property (assign, nonatomic) NSTimeInterval minShowTime;
/**
* HUD隱藏時烤芦,將其從父視圖上移除 举娩。默認值為NO
*/
@property (assign, nonatomic) BOOL removeFromSuperViewOnHide;
/**
* HUD顯示類型,默認為 MBProgressHUDModeIndeterminate.
*/
@property (assign, nonatomic) MBProgressHUDMode mode;
類方法
/**
* 創(chuàng)建HUD,添加到提供的視圖上并顯示
*/
+ (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated;
/**
* 找到最上層的HUD,并隱藏构罗。
*/
+ (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated;
/**
* 在傳入的View上找到最上層的HUD并隱藏此HUD
*/
+ (nullable MBProgressHUD *)HUDForView:(UIView *)view;
實例方法
/**
* 構(gòu)造函數(shù)铜涉,用來初始化HUD
*/
- (instancetype)initWithView:(UIView *)view;
/**
* 顯示HUD
*/
- (void)showAnimated:(BOOL)animated;
/**
* 隱藏HUD
*/
- (void)hideAnimated:(BOOL)animated;
方法調(diào)用流程圖
從MBProgressHUD
提供的主要接口可以看出,主要有顯示HUD和隱藏HUD這兩個功能遂唧,一步步追溯芙代,得出的方法調(diào)用流程圖如下:
方法內(nèi)部實現(xiàn)
方法的內(nèi)部實現(xiàn)主要從兩個方面來分析,顯示HUD和隱藏HUD盖彭。
顯示HUD
首先是MBProgressHUD的構(gòu)造方法
+ (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated {
//初始化MBProgressHUD
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
方法,最后調(diào)用- (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];
//更新指示器
[self updateIndicators];
[self registerForNotifications];
}
添加子視圖都是常見的方式,讓視圖跟隨陀螺儀運動隧熙,這個之前沒有接觸過片挂,后續(xù)需要了解下。
- (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
}
再主要看下更新指示器的代碼贞盯。
- (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) {
// 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];
}
//環(huán)形動畫
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;
}
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];
}
在這個方法中音念,主要是根據(jù)顯示的模式,將不同的indicator
視圖賦值給indicator
屬性躏敢。更新完指示器后闷愤,就是開始將視圖顯示在界面上。調(diào)用的是- (void)showAnimated:(BOOL)animated
方法父丰。
- (void)showAnimated:(BOOL)animated {
//保證當前線程是主線程
MBMainThreadAssert();
[self.minShowTimer invalidate];
self.useAnimation = animated;
self.finished = NO;
// 如果設(shè)置了寬限時間肝谭,則推遲HUD的顯示
if (self.graceTime > 0.0) {
NSTimer *timer = [NSTimer timerWithTimeInterval:self.graceTime
target:self
selector:@selector(handleGraceTimer:)
userInfo:nil
repeats:NO];
//默認把你的Timer以NSDefaultRunLoopMode添加到MainRunLoop上掘宪,而當當前視圖在滾動時,當前的MainRunLoop是處于UITrackingRunLoopMode的模式下攘烛,在這個模式下魏滚,是不會處理NSDefaultRunLoopMode的消息,要想在scrollView滾動的同時Timer也執(zhí)行的話坟漱,我們需要將Timer以NSRunLoopCommonModes的模式注冊到當前RunLoop中.
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
self.graceTimer = timer;
}
// ... otherwise show the HUD immediately
else {
[self showUsingAnimation:self.useAnimation];
}
}
在- (void)showAnimated:(BOOL)animated
方法中鼠次,主要做的是判斷是否設(shè)置了推遲顯示HUD的時間,如果設(shè)置了芋齿,就推遲設(shè)置的時間再顯示腥寇。最后,執(zhí)行- (void)showUsingAnimation:(BOOL)animated
方法觅捆。
- (void)showUsingAnimation:(BOOL)animated {
// Cancel any previous animations
[self.bezelView.layer removeAllAnimations];
[self.backgroundView.layer removeAllAnimations];
// Cancel any scheduled hideDelayed: calls
[self.hideDelayTimer invalidate];
//記錄當前顯示的時間赦役,在HUD隱藏時,比較HUD顯示到HUD隱藏之間的間隔與最小顯示時間栅炒,
//如果小于掂摔,繼續(xù)顯示,直到顯示時間等于最小顯示時間赢赊,再隱藏HUD
self.showStarted = [NSDate date];
self.alpha = 1.f;
// Needed in case we hide and re-show with the same NSProgress object attached.
//好像是通過這個去刷新進度乙漓,這個需要再查下。
[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;
}
}
最后執(zhí)行- (void)animateIn:(BOOL)animatingIn withType:(MBProgressHUDAnimation)type completion:(void(^)(BOOL finished))completion
方法释移,這個方法顯示和隱藏均會調(diào)用叭披。
//這個方法主要對self.bezelView視圖進行動畫
- (void)animateIn:(BOOL)animatingIn withType:(MBProgressHUDAnimation)type completion:(void(^)(BOOL finished))completion {
// Automatically determine the correct zoom animation type
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;
}
// 使用動畫
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];
}
從代碼可以看出,這里只是對指示器的父視圖做了放大縮小的動畫玩讳。
隱藏HUD
+ (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated {
//獲取當前顯示的hud,如果存在涩蜘,當前隱藏時,將其從父視圖移除
MBProgressHUD *hud = [self HUDForView:view];
if (hud != nil) {
hud.removeFromSuperViewOnHide = YES;
[hud hideAnimated:animated];
return YES;
}
return NO;
}
在這個方法的執(zhí)行過程中锋边,調(diào)用- (void)hideAnimated:(BOOL)animated
方法皱坛。
- (void)hideAnimated:(BOOL)animated {
MBMainThreadAssert();
[self.graceTimer invalidate];
self.useAnimation = animated;
self.finished = YES;
// 如果設(shè)置了最小顯示時間编曼,計算HUD顯示時長豆巨,
// 如果HUD顯示時長小于最小顯示時間,延遲顯示
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];
}
- (void)hideAnimated:(BOOL)animated
方法中掐场,主要做的是判斷是否需要推遲隱藏HUD往扔,最后調(diào)用- (void)hideUsingAnimation:(BOOL)animated
方法,
- (void)hideUsingAnimation:(BOOL)animated {
//判斷是否需要動畫效果熊户,如無萍膛,則直接隱藏
if (animated && self.showStarted) {
self.showStarted = nil;
//跟顯示HUD差不多,只是指示器父視圖沒有做放大縮小的動畫
[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];
}
}
最后嚷堡,調(diào)用- (void)done
方法蝗罗。這個方法主要負責屬性的釋放和隱藏完成回調(diào)的處理艇棕。
- (void)done {
// Cancel any scheduled hideDelayed: calls
[self.hideDelayTimer invalidate];
//指示進度的顯示問題,后續(xù)還需再補充
[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];
}
}
總結(jié)
從代碼來看串塑,MBProgressHUD
這個三方庫有幾個地方值借鑒:
-
graceTime
和minShowTime
沼琉,在開發(fā)的時候會出現(xiàn)顯示HUD后,存在緩存或者網(wǎng)速較好時桩匪,HUD顯示到HUD隱藏的時間較短打瘪,界面出現(xiàn)閃動的情況,這時傻昙,就可以通過設(shè)置graceTime
和minShowTime
來處理闺骚,達到更好的用戶體驗。 這個在封裝彈窗控件時妆档,可以參考僻爽。
本文已經(jīng)同步到我的個人技術(shù)博客: 傳送門 ,歡迎常來^^贾惦。