SVProgressHUD
是iOS
開發(fā)中比較常用的一個(gè)三方庫,用來在執(zhí)行耗時(shí)操作或者指示用戶操作結(jié)果的場合,由于使用簡單,功能豐富,交互友好,被廣泛應(yīng)用。本文從源碼的角度,解讀一下實(shí)現(xiàn)的過程,希望能起到拋磚引玉的作用。
一. 效果預(yù)覽
- SVPIndefiniteAnimatedView
- SVProgressAnimatedView
- SVRadialGradientLayer
二. 類分析
-
SVProgressHUD
這是SVProgressHUD
顯示提示框的類,提供類方法和屬性來進(jìn)行不同的設(shè)置。
1. HUD
提示框背景
typedef NS_ENUM(NSInteger, SVProgressHUDStyle) {
SVProgressHUDStyleLight, // 白色
SVProgressHUDStyleDark, // 黑色
SVProgressHUDStyleCustom // 用戶自定義
};
2. 遮罩層背景
typedef NS_ENUM(NSUInteger, SVProgressHUDMaskType) {
SVProgressHUDMaskTypeNone = 1, // 默認(rèn)mask,用戶和交互
SVProgressHUDMaskTypeClear, // 不允許交互
SVProgressHUDMaskTypeBlack, // 不允許交互,遮罩層呈黑色部分透明
SVProgressHUDMaskTypeGradient, // 不允許交互,遮罩層呈漸變效果
SVProgressHUDMaskTypeCustom // 不允許交互,遮罩層顏色自定義
};
3. 無限循環(huán)的顯示類型
typedef NS_ENUM(NSUInteger, SVProgressHUDAnimationType) {
SVProgressHUDAnimationTypeFlat, // SVPIndefiniteAnimatedView
SVProgressHUDAnimationTypeNative // 系統(tǒng)的UIActivityIndicatorView
};
4. 常用屬性介紹
hud
最小尺寸,默認(rèn)是(100,100)
@property (assign, nonatomic) CGSize minimumSize UI_APPEARANCE_SELECTOR;
圓環(huán)厚度,默認(rèn)是2px
@property (assign, nonatomic) CGFloat ringThickness UI_APPEARANCE_SELECTOR;
圓環(huán)半徑,默認(rèn)是18px
@property (assign, nonatomic) CGFloat ringRadius UI_APPEARANCE_SELECTOR;
提示語字體,默認(rèn)是14px
@property (strong, nonatomic) UIFont *font UI_APPEARANCE_SELECTOR;
Image提示框顯示時(shí)間,默認(rèn)是5s
@property (assign, nonatomic) NSTimeInterval minimumDismissTimeInterval;
5. 常用方法介紹
- 無限循環(huán)狀態(tài)顯示,不會(huì)自動(dòng)小時(shí),需主動(dòng)調(diào)用
dismiss
方法
+ (void)show;
+ (void)showWithStatus:(NSString*)status;
+ (void)dismiss;
+ (void)dismissWithCompletion:(SVProgressHUDDismissCompletion)completion;
+ (void)dismissWithDelay:(NSTimeInterval)delay;
+ (void)dismissWithDelay:(NSTimeInterval)delay completion:(SVProgressHUDDismissCompletion)completion;
- 進(jìn)度條狀態(tài)顯示
+ (void)showProgress:(float)progress;
+ (void)showProgress:(float)progress status:(NSString*)status;
- 圖片狀態(tài)顯示
+ (void)showInfoWithStatus:(NSString*)status;
+ (void)showSuccessWithStatus:(NSString*)status;
+ (void)showErrorWithStatus:(NSString*)status;
-
hud
距離中心點(diǎn)的偏移量
+ (void)setOffsetFromCenter:(UIOffset)offset;
+ (void)resetOffsetFromCenter;
6. 通知
通過監(jiān)聽不同的通知事件,可以獲取hud
的狀態(tài)
extern NSString * const SVProgressHUDWillDisappearNotification;
extern NSString * const SVProgressHUDDidDisappearNotification;
extern NSString * const SVProgressHUDWillAppearNotification;
extern NSString * const SVProgressHUDDidAppearNotification;
** 7.hud
顯示流程**
SVProgressHUD
采用單例模式,簡化代碼維護(hù);同時(shí),根據(jù)SVProgressHUD
的層級結(jié)構(gòu)可以看出,從底層到頂層依次是:UIControl (overlayView) -> SVProgressHUD -> UIView (hudView) -> UIVisualEffectView -> AnimatedView
(具體動(dòng)畫視圖) 。
-
-(void)showStatus:(NSString*)status
, 這是顯示無限循環(huán)狀態(tài)的提示框,可以添加文字進(jìn)一步詳細(xì)補(bǔ)充。其中,SVProgressHUD
采用圖形和文字分離的模式,方面文字視圖的復(fù)用。所有,顯示文字的視圖,最終都會(huì)調(diào)用下面的方法。
- (void)showStatus:(NSString*)status {
// 更新frame及位置,因?yàn)閒rame是更加status來確定的而postion是根據(jù)參數(shù)控制的。
[self updateHUDFrame];
[self positionHUD:nil];
// 更新 accesibilty 和是否可以點(diǎn)擊
if(self.defaultMaskType != SVProgressHUDMaskTypeNone) {
self.overlayView.userInteractionEnabled = YES;
self.accessibilityLabel = status;
self.isAccessibilityElement = YES;
} else {
self.overlayView.userInteractionEnabled = NO;
self.hudView.accessibilityLabel = status;
self.hudView.isAccessibilityElement = YES;
}
// 設(shè)置overlayView為透明色
self.overlayView.backgroundColor = [UIColor clearColor];
// 根據(jù)alpha值判斷是是否可見
if(self.alpha != 1.0f || self.hudView.alpha != 1.0f) {
// 如果之前不可見則發(fā)出SVProgressHUDWillAppearNotification通知,告訴馬上顯示
[[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDWillAppearNotification
object:self
userInfo:[self notificationUserInfo]];
// 縮放效果
self.hudView.transform = CGAffineTransformScale(self.hudView.transform, 1.3, 1.3);
// 處理初始值處理iOS7及以上不會(huì)響應(yīng)透明度改變的UIToolbar
self.alpha = 0.0f;
self.hudView.alpha = 0.0f;
// 定義動(dòng)畫block及完成動(dòng)畫block
__weak SVProgressHUD *weakSelf = self;
__block void (^animationsBlock)(void) = ^{
__strong SVProgressHUD *strongSelf = weakSelf;
if(strongSelf) {
// Shrink HUD to finish pop up animation
strongSelf.hudView.transform = CGAffineTransformScale(strongSelf.hudView.transform, 1/1.3f, 1/1.3f);
strongSelf.alpha = 1.0f;
strongSelf.hudView.alpha = 1.0f;
}
};
__block void (^completionBlock)(void) = ^{
__strong SVProgressHUD *strongSelf = weakSelf;
if(strongSelf) {
// Check if we really achieved to show the HUD (<=> alpha values are applied)
// and the change of these values has not been cancelled in between
// e.g. due to a dismissal
if(strongSelf.alpha == 1.0f && strongSelf.hudView.alpha == 1.0f){
// Register observer <=> we now have to handle orientation changes etc.
[strongSelf registerNotifications];
// Post notification to inform user
[[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDDidAppearNotification
object:strongSelf
userInfo:[strongSelf notificationUserInfo]];
}
}
// 更新 accesibilty
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil);
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, status);
};
if (self.fadeInAnimationDuration > 0) {
// 如果設(shè)置了動(dòng)畫時(shí)間則進(jìn)行動(dòng)畫效果
[UIView animateWithDuration:self.fadeInAnimationDuration
delay:0
options:(UIViewAnimationOptions) (UIViewAnimationOptionAllowUserInteraction | UIViewAnimationCurveEaseOut | UIViewAnimationOptionBeginFromCurrentState)
animations:^{
animationsBlock();
} completion:^(BOOL finished) {
completionBlock();
}];
} else {
animationsBlock();
completionBlock();
}
// 完成了更新視圖層次,視圖的frame以及視圖的各種屬性之后,告訴系統(tǒng)稍微進(jìn)行重繪
[self setNeedsDisplay];
}
}
-
-(void)showProgress:(float)progress status:(NSString*)status
,這是顯示單次滾動(dòng)效果的提示框,每次顯示視圖前,都會(huì)取消其它視圖,防止上次顯示不同視圖產(chǎn)生的干擾。其中,在設(shè)置strokeEnd
時(shí),使用事物類CATransaction
,確保操作不被干擾。
- (void)showProgress:(float)progress status:(NSString*)status {
__weak SVProgressHUD *weakSelf = self;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
__strong SVProgressHUD *strongSelf = weakSelf;
if(strongSelf){
// 更新并且檢查視圖層次確保SVProgressHUD可見
[strongSelf updateViewHierarchy];
// 重置imageView和消失時(shí)間。防止之前調(diào)用過,使用上次存在的樣式設(shè)置
strongSelf.imageView.hidden = YES;
strongSelf.imageView.image = nil;
if(strongSelf.fadeOutTimer) {
strongSelf.activityCount = 0;
}
strongSelf.fadeOutTimer = nil;
// 更新statusLabel顯示的內(nèi)容和顯示的進(jìn)度
strongSelf.statusLabel.text = status;
strongSelf.progress = progress;
// 根據(jù)progersss的值來確定正確的樣式,當(dāng)progress>=0的時(shí)候,顯示進(jìn)度樣式,當(dāng)progress = -1的時(shí)候?yàn)闊o限旋轉(zhuǎn)的樣式
if(progress >= 0) {
// 防止上次為無限旋轉(zhuǎn)的樣式導(dǎo)致重疊
[strongSelf cancelIndefiniteAnimatedViewAnimation];
// 添加進(jìn)度視圖到hudview上,并且設(shè)置當(dāng)前進(jìn)度值
if(!strongSelf.ringView.superview)
[strongSelf.hudView addSubview:strongSelf.ringView];
if(!strongSelf.backgroundRingView.superview)
[strongSelf.hudView addSubview:strongSelf.backgroundRingView];
// Set progress animated
[CATransaction begin];
[CATransaction setDisableActions:YES];
strongSelf.ringView.strokeEnd = progress;
[CATransaction commit];
// 更新activityCount
if(progress == 0) {
strongSelf.activityCount++;
}
} else {
// 防止上次為進(jìn)度的樣式導(dǎo)致重疊
[strongSelf cancelRingLayerAnimation];
// 增加無限旋轉(zhuǎn)視圖到hudview上
[strongSelf.hudView addSubview:strongSelf.indefiniteAnimatedView];
if([strongSelf.indefiniteAnimatedView respondsToSelector:@selector(startAnimating)]) {
[(id)strongSelf.indefiniteAnimatedView startAnimating];
}
// Update the activity count
strongSelf.activityCount++;
}
// 顯示提示的文字信息
[strongSelf showStatus:status];
}
}];
}
-
-(void)showImage:(UIImage*)image status:(NSString*)status duration:(NSTimeInterval)duration
,這是顯示帶Image
的提示框,自帶info
/success
/error
三種類型,也可以自定義圖片。
- (void)showImage:(UIImage*)image status:(NSString*)status duration:(NSTimeInterval)duration {
__weak SVProgressHUD *weakSelf = self;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
__strong SVProgressHUD *strongSelf = weakSelf;
if(strongSelf){
// 更新并且檢查視圖層次確保SVProgressHUD可見
[strongSelf updateViewHierarchy];
// 重置progress,并取消其它動(dòng)畫
strongSelf.progress = SVProgressHUDUndefinedProgress;
[strongSelf cancelRingLayerAnimation];
[strongSelf cancelIndefiniteAnimatedViewAnimation];
// 更新imageView
UIColor *tintColor = strongSelf.foregroundColorForStyle;
UIImage *tintedImage = image;
if([strongSelf.imageView respondsToSelector:@selector(setTintColor:)]) {
if (tintedImage.renderingMode != UIImageRenderingModeAlwaysTemplate) {
tintedImage = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
}
strongSelf.imageView.tintColor = tintColor;
} else {
tintedImage = [strongSelf image:image withTintColor:tintColor];
}
strongSelf.imageView.image = tintedImage;
strongSelf.imageView.hidden = NO;
// 更新文字
strongSelf.statusLabel.text = status;
// 顯示文字視圖
[strongSelf showStatus:status];
// 添加定時(shí)消失timer
strongSelf.fadeOutTimer = [NSTimer timerWithTimeInterval:duration target:strongSelf selector:@selector(dismiss) userInfo:nil repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:strongSelf.fadeOutTimer forMode:NSRunLoopCommonModes];
}
}];
}
8. 其它方法
設(shè)置
hud
位置的方法
- (void)positionHUD:(NSNotification*)notification
調(diào)整
hud
尺寸的方法
- (void)updateHUDFrame
更新模糊背景視圖的方法
- (void)updateBlurBounds
-
SVPIndefiniteAnimatedView 類
這個(gè)類提供了一個(gè)無線旋轉(zhuǎn)的動(dòng)畫,實(shí)現(xiàn)方法是把一個(gè)顏色漸變的圖片旋轉(zhuǎn),然后利用UIBezierPath
/CAShapeLayer
/Mask
等遮住不需要的部分,最后利用CABasicAnimation
設(shè)置無限旋轉(zhuǎn)動(dòng)畫。其中,核心部分是利用layer
的mask
屬性實(shí)現(xiàn)遮罩功能,而mask
的實(shí)現(xiàn)方法是顯示顯示bounds的非透明部分,實(shí)例圖如下:
-
SVProgressAnimatedView 類
這個(gè)類提供一個(gè)畫圓環(huán)的視圖,通過不斷改變layer
的strokeEnd
的值,實(shí)現(xiàn)了進(jìn)度的顯示。順便提一下,storkeStart
使用的默認(rèn)值是0, 所以是從正上方開始的。
- (void)setStrokeEnd:(CGFloat)strokeEnd {
_strokeEnd = strokeEnd;
_ringAnimatedLayer.strokeEnd = _strokeEnd;
}
-
SVRadialGradientLayer 類
這個(gè)類繼承自CALayer
,通過CGContextDrawRadialGradient
來畫漸變顏色層;其中,CoreFoundation
中通過create
創(chuàng)建的需要用release
釋放,否則會(huì)造成內(nèi)存泄漏。
至此,SVProgressHUD
分析暫告一段落,分析的不全面的地方,歡迎交流。
參考資料
https://github.com/SVProgressHUD/SVProgressHUD
http://www.lxweimin.com/p/a08d4597cf24