SVProgressHUD 是iOS開發(fā)中比較常用的一個第三方庫,之前好幾個項目都用到了這個庫,但是一個沒有去看它到底是怎么實現(xiàn)的,現(xiàn)在項目基本完成了,深入了解了下SVProgressHUD的源碼,下面是我的理解,如果有什么錯誤的地方,望指正。
實現(xiàn)的效果
SVIndefiniteAnimatedView

SVProgressAnimatedView

SVRadialGradientLayer

類文件分析
整個SVProgressHUD包含4個類和一個bundle文件,除SVProgressHUD類之外,其他都是用來修飾顯示效果的。
SVProgressHUD
用來顯示提示框,通過類方法來設(shè)置屬性和顯示/消失提示框。
1. HUD樣式
typedef NS_ENUM(NSInteger, SVProgressHUDStyle) {
SVProgressHUDStyleLight, // 白色背景
SVProgressHUDStyleDark, // 黑色背景
SVProgressHUDStyleCustom // 自定義
};
2. HUD遮罩層樣式
typedef NS_ENUM(NSUInteger, SVProgressHUDMaskType) {
SVProgressHUDMaskTypeNone = 1, // 允許用戶交互,遮罩層透明
SVProgressHUDMaskTypeClear, // 不允許用戶交互,遮罩層透明
SVProgressHUDMaskTypeBlack, // 不允許用戶交互,遮罩層黑色
SVProgressHUDMaskTypeGradient, // 不允許用戶交互,遮罩層漸變色
SVProgressHUDMaskTypeCustom // 不允許用戶交互,遮罩層自定義顏色
};
3. HUD動畫類型
typedef NS_ENUM(NSUInteger, SVProgressHUDAnimationType) {
SVProgressHUDAnimationTypeFlat, // indefinite animated
SVProgressHUDAnimationTypeNative // iOS 系統(tǒng)提供的 UIActivityIndicatorView
};
4. 常用屬性
UI_APPEARANCE_SELECTOR 可以統(tǒng)一設(shè)置屬性。
@property (assign, nonatomic) CGFloat ringThickness UI_APPEARANCE_SELECTOR; // 圓環(huán)的寬度 默認(rèn)2 pt
@property (assign, nonatomic) CGFloat ringRadius UI_APPEARANCE_SELECTOR; // 圓環(huán)的半徑 默認(rèn)18 pt
@property (assign, nonatomic) CGFloat ringNoTextRadius UI_APPEARANCE_SELECTOR; // 沒有text的時候圓環(huán)的半徑 默認(rèn)24 pt
@property (assign, nonatomic) CGFloat cornerRadius UI_APPEARANCE_SELECTOR; // HUD的圓角 默認(rèn)14 pt
@property (strong, nonatomic, nonnull) UIFont *font UI_APPEARANCE_SELECTOR; // 字體,默認(rèn)使用 [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline]
5. 圖片類型
默認(rèn)是從bundle文件中讀取的
@property (strong, nonatomic, nonnull) UIImage *infoImage UI_APPEARANCE_SELECTOR; // 顯示信息的圖片
@property (strong, nonatomic, nonnull) UIImage *successImage UI_APPEARANCE_SELECTOR; // 成功圖片
@property (strong, nonatomic, nonnull) UIImage *errorImage UI_APPEARANCE_SELECTOR; // 錯誤圖片
6. 常用方法介紹
- 無限循環(huán)狀態(tài)顯示,不會自動消失,需要主動調(diào)用dismiss方法
+ (void)show;
+ (void)showWithStatus:(nullable NSString*)status;
+ (void)dismiss;
+ (void)dismissWithCompletion:(nullable SVProgressHUDDismissCompletion)completion;
+ (void)dismissWithDelay:(NSTimeInterval)delay;
+ (void)dismissWithDelay:(NSTimeInterval)delay completion:(nullable SVProgressHUDDismissCompletion)completion;
- 進度條狀態(tài)顯示
+ (void)showProgress:(float)progress;
+ (void)showProgress:(float)progress status:(nullable NSString*)status;
- 圖片狀態(tài)顯示
+ (void)showInfoWithStatus:(nullable NSString*)status;
+ (void)showSuccessWithStatus:(nullable NSString*)status;
+ (void)showErrorWithStatus:(nullable NSString*)status;
+ (void)showImage:(nonnull UIImage*)image status:(nullable NSString*)status;
-
hud距離中心點的偏移量
+ (void)setOffsetFromCenter:(UIOffset)offset;
+ (void)resetOffsetFromCenter;
7. 通知
監(jiān)聽用戶觸摸事件,HUD顯示和消失事件的通知
extern NSString * _Nonnull const SVProgressHUDDidReceiveTouchEventNotification;
extern NSString * _Nonnull const SVProgressHUDDidTouchDownInsideNotification;
extern NSString * _Nonnull const SVProgressHUDWillDisappearNotification;
extern NSString * _Nonnull const SVProgressHUDDidDisappearNotification;
extern NSString * _Nonnull const SVProgressHUDWillAppearNotification;
extern NSString * _Nonnull const SVProgressHUDDidAppearNotification;
8. 顯示流程
SVProgressHUD 全部采用類方法,使用單例模式初始化HUD對象,層次結(jié)構(gòu)為
UIWindow -> UIControl -> SVProgressHUD -> HUDView -> AnimatedView。
處理顯示邏輯的方法,顯示的時候要回到主線程中去操作,每次顯示之前都會將之前顯示的HUD移除掉,并移除之前的動畫,使用CATransaction避免被干擾。
- (void)showProgress:(float)progress status:(NSString*)status
- (void)showImage:(UIImage*)image status:(NSString*)status duration:(NSTimeInterval)duration
- (void)showProgress:(float)progress status:(NSString*)status {
__weak SVProgressHUD *weakSelf = self;
// 這里并沒有采用GCD的方式 而是使用NSOperationQueue 回到主隊列執(zhí)行operation
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
__strong SVProgressHUD *strongSelf = weakSelf;
if(strongSelf){
// Stop timer
strongSelf.fadeOutTimer = nil;
strongSelf.graceTimer = nil;
// 更新視圖層級 使得HUD可以始終顯示在最外層
// Update / Check view hierarchy to ensure the HUD is visible
[strongSelf updateViewHierarchy];
// Reset imageView and fadeout timer if an image is currently displayed
strongSelf.imageView.hidden = YES;
strongSelf.imageView.image = nil;
// Update text and set progress to the given value
strongSelf.statusLabel.hidden = status.length == 0;
strongSelf.statusLabel.text = status;
strongSelf.progress = progress;
// progress >= 0時 顯示進度條動畫ring
// Choose the "right" indicator depending on the progress
if(progress >= 0) {
// Cancel the indefiniteAnimatedView, then show the ringLayer
[strongSelf cancelIndefiniteAnimatedViewAnimation];
// Add ring to HUD
// ringView 沒有superView 即ringView沒有被添加進view中
if(!strongSelf.ringView.superview){
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
[strongSelf.hudView.contentView addSubview:strongSelf.ringView];
#else
[strongSelf.hudView addSubview:strongSelf.ringView];
#endif
}
// backgroundRingView ringView 同一種類型
if(!strongSelf.backgroundRingView.superview){
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
[strongSelf.hudView.contentView addSubview:strongSelf.backgroundRingView];
#else
[strongSelf.hudView addSubview:strongSelf.backgroundRingView];
#endif
}
// Set progress animated
// CATransaction 動畫的事務(wù)性 這里是顯式事物
[CATransaction begin];
// 不顯示動畫的過程
[CATransaction setDisableActions:YES];
strongSelf.ringView.strokeEnd = progress;
[CATransaction commit];
} else {
// Cancel the ringLayer animation, then show the indefiniteAnimatedView
// 取消progress的動畫,顯示無限循環(huán)的那個動畫
[strongSelf cancelRingLayerAnimation];
// Add indefiniteAnimatedView to HUD
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
[strongSelf.hudView.contentView addSubview:strongSelf.indefiniteAnimatedView];
#else
[strongSelf.hudView addSubview:strongSelf.indefiniteAnimatedView];
#endif
// 開始執(zhí)行動畫
if([strongSelf.indefiniteAnimatedView respondsToSelector:@selector(startAnimating)]) {
[(id)strongSelf.indefiniteAnimatedView startAnimating];
}
}
// Fade in delayed if a grace time is set
if (self.graceTimeInterval > 0.0 && self.backgroundView.alpha == 0.0f) {
strongSelf.graceTimer = [NSTimer timerWithTimeInterval:self.graceTimeInterval target:strongSelf selector:@selector(fadeIn:) userInfo:nil repeats:NO];
// 添加到NSRunLoopCommonModes中 避免其他的RunLoop干擾
[[NSRunLoop mainRunLoop] addTimer:strongSelf.graceTimer forMode:NSRunLoopCommonModes];
} else {
[strongSelf fadeIn:nil];
}
// Tell the Haptics Generator to prepare for feedback, which may come soon
#if TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000
// 告訴觸覺反饋器 開始接收反饋
[strongSelf.hapticGenerator prepare];
#endif
}
}];
}
- (void)fadeIn:(id)data {
// Update the HUDs frame to the new content and position HUD
// 更新HUD的frame
[self updateHUDFrame];
// 更新HUD的位置
[self positionHUD:nil];
// Update accessibility as well as user interaction
if(self.defaultMaskType != SVProgressHUDMaskTypeNone) {
// 不擁護用戶交互 controlView 繼承自UIControl
self.controlView.userInteractionEnabled = YES;
self.accessibilityLabel = self.statusLabel.text ?: NSLocalizedString(@"Loading", nil);
self.isAccessibilityElement = YES;
} else {
// 用戶是可以交互的 可以點擊除顯示HUD之外的區(qū)域
self.controlView.userInteractionEnabled = NO;
self.hudView.accessibilityLabel = self.statusLabel.text ?: NSLocalizedString(@"Loading", nil);
self.hudView.isAccessibilityElement = YES;
}
// Get duration
// 獲取顯示時間
id duration = [data isKindOfClass:[NSTimer class]] ? ((NSTimer *)data).userInfo : data;
// Show if not already visible
if(self.backgroundView.alpha != 1.0f) { // 開始要顯示
// Post notification to inform user
// 通知用戶將要顯示HUD了
[[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDWillAppearNotification
object:self
userInfo:[self notificationUserInfo]];
// Shrink HUD to to make a nice appear / pop up animation
/*
CGAffineTransform CGAffineTransformScale(CGAffineTransform t,
CGFloat sx, CGFloat sy)
t 要進行變換的矩陣
sx x方向上的縮放倍數(shù)
sy y方向上的縮放倍數(shù)
縮小2/3
*/
self.hudView.transform = self.hudView.transform = CGAffineTransformScale(self.hudView.transform, 1/1.5f, 1/1.5f);
// 執(zhí)行完動畫后的回調(diào) 這里寫成block的原因 可以在方法里調(diào)用方法
__block void (^animationsBlock)(void) = ^{
// Zoom HUD a little to make a nice appear / pop up animation
self.hudView.transform = CGAffineTransformIdentity;
// Fade in all effects (colors, blur, etc.)
[self fadeInEffects];
};
// 顯示完成后的回調(diào)
__block void (^completionBlock)(void) = ^{
// Check if we really achieved to show the HUD (<=> alpha)
// and the change of these values has not been cancelled in between e.g. due to a dismissal
if(self.backgroundView.alpha == 1.0f){ // 顯示完成
// Register observer <=> we now have to handle orientation changes etc.
[self registerNotifications];
// Post notification to inform user
[[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDDidAppearNotification
object:self
userInfo:[self notificationUserInfo]];
// Update accessibility
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil);
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, self.statusLabel.text);
// Dismiss automatically if a duration was passed as userInfo. We start a timer
// which then will call dismiss after the predefined duration
if(duration){
// 顯示完成后使用計時器調(diào)用消失的方法
self.fadeOutTimer = [NSTimer timerWithTimeInterval:[(NSNumber *)duration doubleValue] target:self selector:@selector(dismiss) userInfo:nil repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:self.fadeOutTimer forMode:NSRunLoopCommonModes];
}
}
};
// Animate appearance
if (self.fadeInAnimationDuration > 0) {
// Animate appearance
[UIView animateWithDuration:self.fadeInAnimationDuration
delay:0
options:(UIViewAnimationOptions) (UIViewAnimationOptionAllowUserInteraction | UIViewAnimationCurveEaseIn | UIViewAnimationOptionBeginFromCurrentState)
animations:^{
animationsBlock();
} completion:^(BOOL finished) {
completionBlock();
}];
} else {
animationsBlock();
completionBlock();
}
// Inform iOS to redraw the view hierarchy
// 告訴系統(tǒng)更新視圖層級
[self setNeedsDisplay];
} else { // 如果已經(jīng)顯示
// Update accessibility
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil);
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, self.statusLabel.text);
// Dismiss automatically if a duration was passed as userInfo. We start a timer
// which then will call dismiss after the predefined duration
if(duration){
self.fadeOutTimer = [NSTimer timerWithTimeInterval:[(NSNumber *)duration doubleValue] target:self selector:@selector(dismiss) userInfo:nil repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:self.fadeOutTimer forMode:NSRunLoopCommonModes];
}
}
}
處理消失邏輯的方法,同樣需要到主線程中操作。
- (void)dismissWithDelay:(NSTimeInterval)delay completion:(SVProgressHUDDismissCompletion)completion
- (void)dismissWithDelay:(NSTimeInterval)delay completion:(SVProgressHUDDismissCompletion)completion {
__weak SVProgressHUD *weakSelf = self;
// 同show方法
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
__strong SVProgressHUD *strongSelf = weakSelf;
if(strongSelf){
// Stop timer
strongSelf.graceTimer = nil;
// Post notification to inform user
[[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDWillDisappearNotification
object:nil
userInfo:[strongSelf notificationUserInfo]];
__block void (^animationsBlock)(void) = ^{
// Shrink HUD a little to make a nice disappear animation
strongSelf.hudView.transform = CGAffineTransformScale(strongSelf.hudView.transform, 1/1.3f, 1/1.3f);
// Fade out all effects (colors, blur, etc.)
[strongSelf fadeOutEffects];
};
__block void (^completionBlock)(void) = ^{
// Check if we really achieved to dismiss the HUD (<=> alpha values are applied)
// and the change of these values has not been cancelled in between e.g. due to a new show
if(self.backgroundView.alpha == 0.0f){
// Clean up view hierarchy (overlays)
[strongSelf.controlView removeFromSuperview];
[strongSelf.backgroundView removeFromSuperview];
[strongSelf.hudView removeFromSuperview];
[strongSelf removeFromSuperview];
// Reset progress and cancel any running animation
strongSelf.progress = SVProgressHUDUndefinedProgress;
[strongSelf cancelRingLayerAnimation];
[strongSelf cancelIndefiniteAnimatedViewAnimation];
// Remove observer <=> we do not have to handle orientation changes etc.
[[NSNotificationCenter defaultCenter] removeObserver:strongSelf];
// Post notification to inform user
[[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDDidDisappearNotification
object:strongSelf
userInfo:[strongSelf notificationUserInfo]];
// Tell the rootViewController to update the StatusBar appearance
#if !defined(SV_APP_EXTENSIONS) && TARGET_OS_IOS
// 更新狀態(tài)欄
UIViewController *rootController = [[UIApplication sharedApplication] keyWindow].rootViewController;
[rootController setNeedsStatusBarAppearanceUpdate];
#endif
// Run an (optional) completionHandler
if (completion) {
completion();
}
}
};
// UIViewAnimationOptionBeginFromCurrentState AND a delay doesn't always work as expected
// When UIViewAnimationOptionBeginFromCurrentState is set, animateWithDuration: evaluates the current
// values to check if an animation is necessary. The evaluation happens at function call time and not
// after the delay => the animation is sometimes skipped. Therefore we delay using dispatch_after.
dispatch_time_t dipatchTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC));
dispatch_after(dipatchTime, dispatch_get_main_queue(), ^{
if (strongSelf.fadeOutAnimationDuration > 0) {
// Animate appearance
[UIView animateWithDuration:strongSelf.fadeOutAnimationDuration
delay:0
options:(UIViewAnimationOptions) (UIViewAnimationOptionAllowUserInteraction | UIViewAnimationCurveEaseOut | UIViewAnimationOptionBeginFromCurrentState)
animations:^{
animationsBlock();
} completion:^(BOOL finished) {
completionBlock();
}];
} else {
animationsBlock();
completionBlock();
}
});
// Inform iOS to redraw the view hierarchy
// 告訴系統(tǒng)更新視圖層級
[strongSelf setNeedsDisplay];
}
}];
}
SVIndefiniteAnimatedView
這個類提供了無限旋轉(zhuǎn)動畫,可以單獨拿出來使用,實現(xiàn)思路是
初始化兩個layer對象,indefiniteAnimatedLayer和maskLayer,maskLayer有一張圖片,設(shè)置indefiniteAnimatedLayer的mask為maskLayer,這樣就得到一張漸變的layer。分別設(shè)置layer和layer.mask的動畫達到效果。示例圖:

SVProgressAnimatedView
這個類提供環(huán)形加載效果,通過不斷修改strokeEnd的值,實現(xiàn)了進度的顯示。
- (void)setStrokeEnd:(CGFloat)strokeEnd {
_strokeEnd = strokeEnd;
_ringAnimatedLayer.strokeEnd = _strokeEnd;
}
SVRadialGradientLayer
繼承自CALayer,使用CGContextDrawRadialGradient方法畫漸變層。
以上,就是暫時對SVProgressHUD的理解,理解不夠的地方,歡迎交流。