`SVProgressHUD iOS`開發中比較常用的一個三方庫,用來在執行耗時操作或者指示用戶操作結果的場合,由於使用簡單,功能豐富,交互友好,被廣泛應用。本文從源碼的角度,解讀一下實現的過程,希望能起到拋磚引玉的作用。 一. 效果預覽 1. SVPIndefiniteAnimatedView 2 ...
SVProgressHUD
是iOS
開發中比較常用的一個三方庫,用來在執行耗時操作或者指示用戶操作結果的場合,由於使用簡單,功能豐富,交互友好,被廣泛應用。本文從源碼的角度,解讀一下實現的過程,希望能起到拋磚引玉的作用。
一. 效果預覽
1. SVPIndefiniteAnimatedView
2. SVProgressAnimatedView
3. SVRadialGradientLayer
二. 類分析
1. SVProgressHUD
這是SVProgressHUD
顯示提示框的類,提供類方法和屬性來進行不同的設置。
** HUD
提示框背景
typedef NS_ENUM(NSInteger, SVProgressHUDStyle) {
SVProgressHUDStyleLight, // 白色
SVProgressHUDStyleDark, // 黑色
SVProgressHUDStyleCustom // 用戶自定義
};
** 遮罩層背景
typedef NS_ENUM(NSUInteger, SVProgressHUDMaskType) {
SVProgressHUDMaskTypeNone = 1, // 預設mask,用戶和交互
SVProgressHUDMaskTypeClear, // 不允許交互
SVProgressHUDMaskTypeBlack, // 不允許交互,遮罩層呈黑色部分透明
SVProgressHUDMaskTypeGradient, // 不允許交互,遮罩層呈漸變效果
SVProgressHUDMaskTypeCustom // 不允許交互,遮罩層顏色自定義
};
** 無限迴圈的顯示類型
typedef NS_ENUM(NSUInteger, SVProgressHUDAnimationType) {
SVProgressHUDAnimationTypeFlat, // SVPIndefiniteAnimatedView
SVProgressHUDAnimationTypeNative // 系統的UIActivityIndicatorView
};
**常用屬性介紹
hud
最小尺寸,預設是(100,100)
@property (assign, nonatomic) CGSize minimumSize UI_APPEARANCE_SELECTOR;
圓環厚度,預設是2px
@property (assign, nonatomic) CGFloat ringThickness UI_APPEARANCE_SELECTOR;
圓環半徑,預設是18px
@property (assign, nonatomic) CGFloat ringRadius UI_APPEARANCE_SELECTOR;
提示語字體,預設是14px
@property (strong, nonatomic) UIFont *font UI_APPEARANCE_SELECTOR;
Image提示框顯示時間,預設是5s
@property (assign, nonatomic) NSTimeInterval minimumDismissTimeInterval;
** 常用方法介紹
- 無限迴圈狀態顯示,不會自動小時,需主動調用
dismiss
方法
+ (void)show;
+ (void)showWithStatus:(NSString*)status;
+ (void)dismiss;
+ (void)dismissWithCompletion:(SVProgressHUDDismissCompletion)completion;
+ (void)dismissWithDelay:(NSTimeInterval)delay;
+ (void)dismissWithDelay:(NSTimeInterval)delay completion:(SVProgressHUDDismissCompletion)completion;
- 進度條狀態顯示
+ (void)showProgress:(float)progress;
+ (void)showProgress:(float)progress status:(NSString*)status;
- 圖片狀態顯示
+ (void)showInfoWithStatus:(NSString*)status;
+ (void)showSuccessWithStatus:(NSString*)status;
+ (void)showErrorWithStatus:(NSString*)status;
hud
距離中心點的偏移量
+ (void)setOffsetFromCenter:(UIOffset)offset;
+ (void)resetOffsetFromCenter;
** 通知
通過監聽不同的通知事件,可以獲取hud
的狀態
extern NSString * const SVProgressHUDWillDisappearNotification;
extern NSString * const SVProgressHUDDidDisappearNotification;
extern NSString * const SVProgressHUDWillAppearNotification;
extern NSString * const SVProgressHUDDidAppearNotification;
** hud
顯示流程
SVProgressHUD
採用單例模式,簡化代碼維護;同時,根據SVProgressHUD
的層級結構可以看出,從底層到頂層依次是:UIControl (overlayView) -> SVProgressHUD -> UIView (hudView) -> UIVisualEffectView -> AnimatedView
(具體動畫視圖) 。
-(void)showStatus:(NSString*)status
, 這是顯示無限迴圈狀態的提示框,可以添加文字進一步詳細補充。其中,SVProgressHUD
採用圖形和文字分離的模式,方面文字視圖的復用。所有,顯示文字的視圖,最終都會調用下麵的方法。
- (void)showStatus:(NSString*)status {
// 更新frame及位置,因為frame是更加status來確定的而postion是根據參數控制的。
[self updateHUDFrame];
[self positionHUD:nil];
// 更新 accesibilty 和是否可以點擊
if(self.defaultMaskType != SVProgressHUDMaskTypeNone) {
self.accessibilityLabel = status;
self.isAccessibilityElement = YES;
} else {
self.hudView.accessibilityLabel = status;
self.hudView.isAccessibilityElement = YES;
}
// Show if not already visible
// Checking one alpha value is sufficient as they are all the same
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
if(self.hudView.contentView.alpha != 1.0f){
#else
根據alpha值判斷是是否可見
if(self.hudView.alpha != 1.0f){
#endif
// 如果之前不可見則發出SVProgressHUDWillAppearNotification通知,告訴馬上顯示
[[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDWillAppearNotification
object:self
userInfo:[self notificationUserInfo]];
//縮放效果
self.hudView.transform = CGAffineTransformScale(self.hudView.transform, 1.3, 1.3);
// Activate blur on view before animation on older iOS versions,
// as we cannot animate this property and use alpha values instead
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
bool greateriOS9 = kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_9_0;
if (self.defaultStyle != SVProgressHUDStyleCustom && !greateriOS9) {
[self addBlur];
// Update alpha
self.hudView.contentView.alpha = 1.0f;
}
#endif
// Define blocks
__block void (^animationsBlock)(void) = ^{
// Shrink HUD to finish pop up animation
self.hudView.transform = CGAffineTransformScale(self.hudView.transform, 1/1.3f, 1/1.3f);
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
if(self.defaultStyle != SVProgressHUDStyleCustom && greateriOS9){
// Fade in blur effect
[self addBlur];
// Update alpha
self.hudView.contentView.alpha = 1.0f;
} else {
// This gives a warning on iOS 8, however it works, see #703
self.hudView.alpha = 1.0f;
}
#else
self.hudView.alpha = 1.0f;
self.hudView.contentView.alpha = 1.0f;
#endif
self.backgroundView.alpha = 1.0f;
};
__block void (^completionBlock)(void) = ^{
// 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
// Checking one alpha value is sufficient as they are all the same
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
if(self.hudView.contentView.alpha == 1.0f){
#else
if(self.hudView.alpha == 1.0f){
#endif
// 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]];
}
// 更新 accesibilty
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil);
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, status);
};
if (self.fadeInAnimationDuration > 0) {
// 如果設置了動畫時間則進行動畫效果
[UIView animateWithDuration:self.fadeInAnimationDuration
delay:0
options:(UIViewAnimationOptions) (UIViewAnimationOptionAllowUserInteraction | UIViewAnimationCurveEaseIn | UIViewAnimationOptionBeginFromCurrentState)
animations:^{
animationsBlock();
} completion:^(BOOL finished) {
completionBlock();
}];
} else {
animationsBlock();
completionBlock();
}
// 完成了更新視圖層次,視圖的frame以及視圖的各種屬性之後,告訴系統稍微進行重繪
[self setNeedsDisplay];
}
}
-(void)showProgress:(float)progress status:(NSString*)status
,這是顯示單次滾動效果的提示框,每次顯示視圖前,都會取消其它視圖,防止上次顯示不同視圖產生的干擾。其中,在設置strokeEnd
時,使用事物類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和消失時間。防止之前調用過,使用上次存在的樣式設置
strongSelf.imageView.hidden = YES;
strongSelf.imageView.image = nil;
if(strongSelf.fadeOutTimer) {
strongSelf.activityCount = 0;
}
strongSelf.fadeOutTimer = nil;
// 更新statusLabel顯示的內容和顯示的進度
strongSelf.statusLabel.text = status;
strongSelf.progress = progress;
//根據progersss的值來確定正確的樣式,當progress>=0的時候,顯示進度樣式,當progress = -1的時候為無限旋轉的樣式
if(progress >= 0) {
// 防止上次為無限旋轉的樣式導致重疊
[strongSelf cancelIndefiniteAnimatedViewAnimation];
// 添加進度視圖到hudview上,並且設置當前進度值
if(!strongSelf.ringView.superview){
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
[strongSelf.hudVibrancyView.contentView addSubview:strongSelf.ringView];
#else
[strongSelf.hudView addSubview:strongSelf.ringView];
#endif
}
if(!strongSelf.backgroundRingView.superview){
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
[strongSelf.hudVibrancyView.contentView addSubview:strongSelf.backgroundRingView];
#else
[strongSelf.hudView addSubview:strongSelf.backgroundRingView];
#endif
}
// Set progress animated
[CATransaction begin];
[CATransaction setDisableActions:YES];
strongSelf.ringView.strokeEnd = progress;
[CATransaction commit];
// 更新activityCount
if(progress == 0) {
strongSelf.activityCount++;
}
} else {
// 防止上次為進度的樣式導致重疊
[strongSelf cancelRingLayerAnimation];
// 增加無限旋轉視圖到hudview上
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
[strongSelf.hudVibrancyView.contentView addSubview:strongSelf.indefiniteAnimatedView];
#else
[strongSelf.hudView addSubview:strongSelf.indefiniteAnimatedView];
#endif
if([strongSelf.indefiniteAnimatedView respondsToSelector:@selector(startAnimating)]) {
[(id)strongSelf.indefiniteAnimatedView startAnimating];
}
// Update the activity count
strongSelf.activityCount++;
}
// 顯示提示的文字信息
[strongSelf showStatus:status];
// 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)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,並取消其它動畫
strongSelf.progress = SVProgressHUDUndefinedProgress;
[strongSelf cancelRingLayerAnimation];
[strongSelf cancelIndefiniteAnimatedViewAnimation];
// 更新imageView
UIColor *tintColor = strongSelf.foregroundColorForStyle;
UIImage *tintedImage = image;
if (image.renderingMode != UIImageRenderingModeAlwaysTemplate) {
tintedImage = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
}
strongSelf.imageView.tintColor = tintColor;
strongSelf.imageView.image = tintedImage;
strongSelf.imageView.hidden = NO;
// 更新文字
strongSelf.statusLabel.text = status;
// 顯示文字視圖
[strongSelf showStatus:status];
// An image will be dismissed automatically. Therefore, we start a timer
// which then will call dismiss after the predefined duration
// 添加定時消失timer
strongSelf.fadeOutTimer = [NSTimer timerWithTimeInterval:duration target:strongSelf selector:@selector(dismiss) userInfo:nil repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:strongSelf.fadeOutTimer forMode:NSRunLoopCommonModes];
}
}];
}
**其它方法
- 設置
hud
位置的方法
- (void)positionHUD:(NSNotification*)notification
- 調整
hud
尺寸的方法
- (void)updateHUDFrame
- 更新模糊背景視圖的方法
- (void)updateBlurBounds
2. SVPIndefiniteAnimatedView 類
這個類提供了一個無線旋轉的動畫,實現方法是把一個顏色漸變的圖片旋轉,然後利用UIBezierPath
/CAShapeLayer
/Mask
等遮住不需要的部分,最後利用CABasicAnimation
設置無限旋轉動畫。其中,核心部分是利用layer
的mask
屬性實現遮罩功能,而mask
的實現方法是顯示顯示bounds的非透明部分,實例圖如下:
3. SVProgressAnimatedView 類
這個類提供一個畫圓環的視圖,通過不斷改變layer
的strokeEnd
的值,實現了進度的顯示。順便提一下,storkeStart
使用的預設值是0, 所以是從正上方開始的。
- (void)setStrokeEnd:(CGFloat)strokeEnd {
_strokeEnd = strokeEnd;
_ringAnimatedLayer.strokeEnd = _strokeEnd;
}
4. SVRadialGradientLayer 類
這個類繼承自CALayer
,通過CGContextDrawRadialGradient
來畫漸變顏色層;其中,CoreFoundation
中通過create
創建的需要用release
釋放,否則會造成記憶體泄漏。
至此,SVProgressHUD
分析暫告一段落,分析的不全面的地方,歡迎交流。
參考資料
https://github.com/SVProgressHUD/SVProgressHUD
http://www.jianshu.com/p/a08d4597cf24