有段時間沒有寫了。這個周末抽空簡單整理了一下關于自己對SVProgressHUD一些看法以及感悟。過程中自己感受到堅持做開源和堅持寫原創文章的不易。時間是每一個程序員最寶貴的資源。
簡介
SVProgressHUD在iOS開發中用作提示的場景還是非常多的。這里主要從整個項目的使用及源碼方面就行分析以及附上相關效果圖。希望能起到拋磚引玉的作用。
使用
SVProgrossHUD是通過單例的方式來使用,這種方式也是許多第三方所使用的。也就是快速創建,不需要手動的alloc進行實例化。
- 使用的場景: 比較合理的場景是在推薦用戶操作之前確定需要執行任務其他任務的時候,而不是在刷新,無限的滑動或者發送消息等場景。
常見的使用方式如下:
[SVProgressHUD show];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 處理耗時的操作
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD dismiss];
});
});
使用
+ (void)show; + (void)showWithStatus:(NSString*)string;
來顯示狀態不明確的操作,用+ (void)showProgress:(CGFloat)progress;+ (void)showProgress:(CGFloat)progress status:(NSString*)status;
來顯示狀態明確的操作,顯示當前操作的進度。
取消
+ (void)dismiss;+ (void)dismissWithDelay:(NSTimeInterval)delay;
這里順便提一下dismissWithDelay
這個方法之前沒注意。可以延遲取消,這樣就不用手動用GCD的延遲去dismiss了。
如果想平衡調用的次數,可以使用
+ (void)popActivity;
一旦匹配了調用show
的次數則會消失。如果沒有匹配爭取則不會消失。其源碼為+ (void)popActivity {
if([self sharedView].activityCount > 0) {
[self sharedView].activityCount--;
}
if([self sharedView].activityCount == 0) {
[[self sharedView] dismiss];
}
}
或者根據字符串的長度來自動確定顯示的時間。當調用下面的這些方法的時候會用這種方式
- (void)showInfoWithStatus:(NSString*)string;
- (void)showSuccessWithStatus:(NSString*)string;
- (void)showErrorWithStatus:(NSString*)string;
- (void)showImage:(UIImage)image status:(NSString)string;
我們可以自定義里面的一些屬性,比如字體大小,提示圖片等。可以自定的方法如下:
+ (void)setDefaultStyle:(SVProgressHUDStyle)style; // default is SVProgressHUDStyleLight
- (void)setDefaultMaskType:(SVProgressHUDMaskType)maskType; // default is SVProgressHUDMaskTypeNone
- (void)setDefaultAnimationType:(SVProgressHUDAnimationType)type; // default is SVProgressHUDAnimationTypeFlat
- (void)setMinimumSize:(CGSize)minimumSize; // default is CGSizeZero, can be used to avoid resizing for a larger message
- (void)setRingThickness:(CGFloat)width; // default is 2 pt
- (void)setRingRadius:(CGFloat)radius; // default is 18 pt
- (void)setRingNoTextRadius:(CGFloat)radius; // default is 24 pt
- (void)setCornerRadius:(CGFloat)cornerRadius; // default is 14 pt
- (void)setFont:(UIFont*)font; // default is [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline]
- (void)setForegroundColor:(UIColor*)color; // default is [UIColor blackColor], only used for SVProgressHUDStyleCustom
- (void)setBackgroundColor:(UIColor*)color; // default is [UIColor whiteColor], only used for SVProgressHUDStyleCustom
- (void)setBackgroundLayerColor:(UIColor*)color; // default is [UIColor colorWithWhite:0 alpha:0.4], only used for SVProgressHUDMaskTypeCustom
- (void)setInfoImage:(UIImage*)image; // default is the bundled info image provided by Freepik
- (void)setSuccessImage:(UIImage*)image; // default is bundled success image from Freepik
- (void)setErrorImage:(UIImage*)image; // default is bundled error image from Freepik
- (void)setViewForExtension:(UIView*)view; // default is nil, only used if #define SV_APP_EXTENSIONS is set
- (void)setMinimumDismissTimeInterval:(NSTimeInterval)interval; // default is 5.0 seconds
- (void)setFadeInAnimationDuration:(NSTimeInterval)duration; // default is 0.15 seconds
- (void)setFadeOutAnimationDuration:(NSTimeInterval)duration; // default is 0.15 seconds
SVProgressHUD默認提供兩種樣式
SVProgressHUDStyleLight, SVProgressHUDStyleDark
,一個是白色主題,一個是黑色主題如果想自定義一些顏色可以通過setForegroundColor and setBackgroundColor
不要忘記設置默認樣式SVProgressHUDStyleCustom
通知,SVProgressHUD會使用到四個通知
SVProgressHUDWillAppearNotification
SVProgressHUDDidAppearNotification
SVProgressHUDWillDisappearNotification
SVProgressHUDDidDisappearNotification
每一通知會傳遞一個userinfo字典傳遞HUD的提示信息,key為``SVProgressHUDStatusUserInfoKey``。當用戶觸摸提示的整個屏幕的時候會發出``SVProgressHUDDidReceiveTouchEventNotification``通知,當用戶直接觸摸HUD的時候會發出``SVProgressHUDDidTouchDownInsideNotification``通知。
關鍵類
SVProgroessHUD一共有四個重要的類。它們分別是
- SVPIndefiniteAnimatedView:無限旋轉視圖組件。如下圖:
-
SVProgressAnimatedView:進度視圖組件.如下圖
SVProgressHUD: 視圖顯示控制類(我們通過SVProgressHUD這個類來使用上兩種視圖組件)。類似于一個管理類。
- SVRadialGradientLayer:漸變層,當我們設置遮罩樣式為
SVProgressHUDMaskTypeGradient
,就需要用到這個層。模仿系統UIAlterView
的背景效果。
- SVProgressHUD.bundle: 這里面放的是一些圖片資源文件
關鍵類分析
SVPIndefiniteAnimatedView
關于這個類,主要是需要講的就是一個如果實現無限加載的動畫效果。如上圖的上圖一樣。原理其實不難,我這個給出一個圖,大家應該就明白了。
- 原理也就是不斷地旋轉一張具有漸變顏色的圖片,然后通過使用mask來遮住不需要的部分(結合layer使用)。
講到這里就不得不提到iOS動畫中的CALayer以及Mask。常見的場景就是CAShapeLayer和mask結合使用。
/* A layer whose alpha channel is used as a mask to select between the
* layer's background and the result of compositing the layer's
* contents with its filtered background. Defaults to nil. When used as
* a mask the layer's `compositingFilter' and `backgroundFilters'
* properties are ignored. When setting the mask to a new layer, the
* new layer must have a nil superlayer, otherwise the behavior is
* undefined. Nested masks (mask layers with their own masks) are
* unsupported. */
@property(nullable, strong) CALayer *mask;
以上是CALayer的頭文件關于mask的說明,mask實際上layer內容的一個遮罩。
如果我們把mask是透明的,實際看到的layer是完全透明的,也就是說只有mask的內容不透明的部分和layer的疊加部分才會顯示。如下圖:
有許多很炫酷的動畫效果都是通過這樣實現的。比如以下幾種
其中還有Twitter的啟動效果
- 代碼片段
// 初始化,設置參數
_indefiniteAnimatedLayer = [CAShapeLayer layer];
_indefiniteAnimatedLayer.contentsScale = [[UIScreen mainScreen] scale];
_indefiniteAnimatedLayer.frame = CGRectMake(0.0f, 0.0f, arcCenter.x*2, arcCenter.y*2);
_indefiniteAnimatedLayer.fillColor = [UIColor clearColor].CGColor;
_indefiniteAnimatedLayer.strokeColor = self.strokeColor.CGColor;
_indefiniteAnimatedLayer.lineWidth = self.strokeThickness;
_indefiniteAnimatedLayer.lineCap = kCALineCapRound;
_indefiniteAnimatedLayer.lineJoin = kCALineJoinBevel;
_indefiniteAnimatedLayer.path = smoothedPath.CGPath;
// 初始化mask,從資源庫中讀取圖片
CALayer *maskLayer = [CALayer layer];
NSBundle *bundle = [NSBundle bundleForClass:[SVProgressHUD class]];
NSURL *url = [bundle URLForResource:@"SVProgressHUD" withExtension:@"bundle"];
NSBundle *imageBundle = [NSBundle bundleWithURL:url];
NSString *path = [imageBundle pathForResource:@"angle-mask" ofType:@"png"];
// 大部分用法都是類似的,通過圖片來作為maskLayer的contents
maskLayer.contents = (__bridge id)[[UIImage imageWithContentsOfFile:path] CGImage];
maskLayer.frame = _indefiniteAnimatedLayer.bounds;
_indefiniteAnimatedLayer.mask = maskLayer;
開始做動畫,做動畫分為了兩個部分,一個是圖片旋轉,一個是動畫組
- 旋轉動畫
// 設置動畫的延遲及類型
NSTimeInterval animationDuration = 1;
CAMediaTimingFunction *linearCurve = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
// 注意value類型為id類型
animation.fromValue = (id) 0;
animation.toValue = @(M_PI*2);
animation.duration = animationDuration;
animation.timingFunction = linearCurve;
// 這個參數不要忘了,是在昨晚動畫之后保持動畫完成的狀態
animation.removedOnCompletion = NO;
animation.repeatCount = INFINITY;
animation.fillMode = kCAFillModeForwards;
animation.autoreverses = NO;
// 將動畫加到mask上
[_indefiniteAnimatedLayer.mask addAnimation:animation forKey:@"rotate"];
通過旋轉動畫我們看到的就是
然后來看看動畫組
// 創建動畫組,并設置相關屬性
CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
animationGroup.duration = animationDuration;
animationGroup.repeatCount = INFINITY;
animationGroup.removedOnCompletion = NO;
animationGroup.timingFunction = linearCurve;
// strokeStart動畫
CABasicAnimation *strokeStartAnimation = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
strokeStartAnimation.fromValue = @0.015;
strokeStartAnimation.toValue = @0.515;
// strokeEnd動畫
CABasicAnimation *strokeEndAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
strokeEndAnimation.fromValue = @0.485;
strokeEndAnimation.toValue = @0.985;
// 將動畫加到動畫組
animationGroup.animations = @[strokeStartAnimation, strokeEndAnimation];
[_indefiniteAnimatedLayer addAnimation:animationGroup forKey:@"progress"];
動畫組的效果
讓我們來找找數字之間的關系
strokeStartAnimation.fromValue = @0.015;
strokeStartAnimation.toValue = @0.515;
strokeEndAnimation.fromValue = @0.485;
strokeEndAnimation.toValue = @0.985;
有些規律吧。這樣就能達到不斷改變strokeStart和strokeEnd的值并讓之間的差值為一個常量。我們看到的就是一個白色的缺口不斷旋轉的效果。個人覺得實現這種效果是想到巧妙的
改變strokeEnd參數的效果
strokeEndAnimation.fromValue = @0.285;
strokeEndAnimation.toValue = @0.785;
- 其他
其他值得學習的大概應該算用到了懶加載的方式吧。便于代碼的管理以及縷清邏輯關系。重寫屬性的setter方法,在setter方法里面完成和這個屬性相關一些賦值,邏輯判斷操作。比如:
- (void)setRadius:(CGFloat)radius {
if(radius != _radius) {
_radius = radius;
// 在setter方法中進行相關邏輯判斷,
[_indefiniteAnimatedLayer removeFromSuperlayer];
_indefiniteAnimatedLayer = nil;
if(self.superview) {
[self layoutAnimatedLayer];
}
}
}
SVProgressAnimatedView
這個是用于處理進度的視圖組件,實現進度的原理也很簡單,也就是不斷改變strokeEnd的值。
來看看那.h文件
@interface SVProgressAnimatedView : UIView
// 半徑
@property (nonatomic, assign) CGFloat radius;
// 厚度
@property (nonatomic, assign) CGFloat strokeThickness;
// 進度指示顏色
@property (nonatomic, strong) UIColor *strokeColor;
// 當前進度,
@property (nonatomic, assign) CGFloat strokeEnd;
@end
.m文件的實現大致和SVIndefiniteAnimatedView一樣。使用懶加載,在willMoveToSuperview
方法中添加layer。實現進度的關鍵就是重寫strokeEnd的setter方法
- (void)setStrokeEnd:(CGFloat)strokeEnd {
_strokeEnd = strokeEnd;
// 改變結束的位置
_ringAnimatedLayer.strokeEnd = _strokeEnd;
}
進度寫死的效果0.4
順便提一下,storkeStart使用的默認值是0。所以是從正上方開始的。
SVProgressHUD
這個類的作用想到于管理類的作用,負責和外部交互和調用視圖組件。進行重要邏輯判斷。
.h文件
extern相關
來看看.h文件中extern的使用
extern NSString * const SVProgressHUDDidReceiveTouchEventNotification;
extern NSString * const SVProgressHUDDidTouchDownInsideNotification;
extern NSString * const SVProgressHUDWillDisappearNotification;
extern NSString * const SVProgressHUDDidDisappearNotification;
extern NSString * const SVProgressHUDWillAppearNotification;
extern NSString * const SVProgressHUDDidAppearNotification;
extern NSString * const SVProgressHUDStatusUserInfoKey;
與extern相關的還有const,static等。下面擴展一下
const 最好理解,修飾的東西不能被修改.指針類型根據位置的不同可以理解成3種情況:
常量指針
初始化之后不能賦值,指向的對象可以是任意對象,對象可變。
NSString * const pt1;
- 指向常量的指針
初始化之后可以賦值,即指向別的常量,指針本身的值可以修改,指向的值不能修改
const NSString * pt2;
- 指向常量的常量指針
const NSString * const pt3;
- extern
等同于c,全局變量的定義,聲明
extern const NSString * AA;
定義
const NSString * AA = @"abc";
- static
等同于c,將變量的作用域限定于本文件?
不同于java C++里面的類變量,oc沒有類變量
--
結論
static
// static變量屬于本類,不同的類對應的是不同的對象
// static變量同一個類所有對象中共享,只初始化一次const
// static const變量同static的結論I,只是不能修改了,但是還是不同的對象
// extern const變量只有一個對象,標準的常量的定義方法
// extern的意思就是這個變量已經定義了,你只負責用就行了
typedef NS_ENUM
定義常見的枚舉,注意命令的方式,***Type
值的命名方式 ***TypeLight
UI_APPEARANCE_SELECTOR
UI_APPEARANCE_SELECTOR:這個關鍵字是外觀屬性都用到的,用一個例子簡單說一下它的作用。
[[UIBarButtonItem appearance] setTintColor:[UIColor redColor]];
可以定制應用中所有條形按鈕的顏色為redColor。沒有這個UI_APPEARANCE_SELECTOR之前,只要一個一個控件的去修改。也就是通過UI_APPEARANCE_SELECTOR可以批量設置控件的顏色了。
深入了解可以看看使用UIAppearance協議自定義視圖
當我在做公用組件的時候,一定要記得把默認值是什么要說明一下。
類方法
類方法主要有兩大類,一種是set***
一種是show**
。前者用于設置外觀樣式,后者是直接使用的方式。
比如:
- set**
+ (void)setDefaultStyle:(SVProgressHUDStyle)style; // default is SVProgressHUDStyleLight
+ (void)setDefaultMaskType:(SVProgressHUDMaskType)maskType; // default is SVProgressHUDMaskTypeNone
+ (void)setDefaultAnimationType:(SVProgressHUDAnimationType)type; // default is SVProgressHUDAnimationTypeFlat
- show**
+ (void)show;
+ (void)showWithMaskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use show and setDefaultMaskType: instead.")));
+ (void)showWithStatus:(NSString*)status;
+ (void)showWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use showWithStatus: and setDefaultMaskType: instead.")));
這里可以注意一下如何標示方法過期的方式maskType __attribute__((deprecated("Use showSuccessWithStatus: and setDefaultMaskType: instead.")))
.m文件
.m文件包含的內容是整個項目中最復雜或者說是最需要梳理與值得學習的。下面重點介紹下.m文件里面的內容。
常量的定義
關于define和statc const定義常量的區別,這里就不講了。主要是提醒一下,盡量用statci const來定義更符合風格吧。比如
static const CGFloat SVProgressHUDParallaxDepthPoints = 10;
readonly及getter的使用
雖然這樣的用法有些麻煩,對于有強迫癥的程序員還是蠻推薦這種寫法的
@property (nonatomic, readonly, getter = isClear) BOOL clear;
getter方法
- (BOOL)isClear {
return (self.defaultMaskType == SVProgressHUDMaskTypeClear ||
self.defaultMaskType == SVProgressHUDMaskTypeNone);
}
事先定義好私有方法,也就是外界不能直接調用的實例方法
這種習慣能夠快速的了解整個類一共有哪些方法以及方法歸類等。比如:
- (void)setStatus:(NSString*)status;
- (void)setFadeOutTimer:(NSTimer*)timer;
- (void)registerNotifications;
- (NSDictionary*)notificationUserInfo;
- (void)positionHUD:(NSNotification*)notification;
- (void)moveToPoint:(CGPoint)newCenter rotateAngle:(CGFloat)angle;
- (void)overlayViewDidReceiveTouchEvent:(id)sender forEvent:(UIEvent*)event;
這樣比較有條理,私有方法的定義是在類的擴展里面。
使用單例
常見的一些關于UI的第三方都是通過類方法調用,而且全局可以只用一個實例對象來維護就可以了。
+ (SVProgressHUD*)sharedView {
static dispatch_once_t once;
static SVProgressHUD *sharedView;
#if !defined(SV_APP_EXTENSIONS)
// 創建單例對象
dispatch_once(&once, ^{ sharedView = [[self alloc] initWithFrame:[[[UIApplication sharedApplication] delegate] window].bounds]; });
#else
dispatch_once(&once, ^{ sharedView = [[self alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; });
#endif
return sharedView;
}
show方法參數化
關于方法的參數化說白了就是為了實現代碼的復用。因為有存在相同的邏輯,則把相同部分抽離出來,不同的部分通過傳入不同參數來控制來達到代碼的復用。在實際工作中,這一點也非常重要。
經過整理,最終得出所有調用show**
方法最終調用的只有兩個個方法+ (void)showProgress:(float)progress status:(NSString*)status
和+ (void)showImage:(UIImage*)image status:(NSString*)status
- showProgress:(float)progress status:(NSString*)status
當顯示的是無限旋轉提示的時候,會傳入progrerss = -1來區別顯示進度的樣式。
+ (void)showWithStatus:(NSString*)status {
[self sharedView];
[self showProgress:SVProgressHUDUndefinedProgress status:status];
}
這里的SVProgressHUDUndefinedProgress
其實是一個常量。其定義為static const CGFloat SVProgressHUDUndefinedProgress = -1;
這里需要提一提的是,設置遮罩樣式沒有通過參數傳遞來設置而是通過設置屬性的方式來做的。
+ (void)showProgress:(float)progress maskType:(SVProgressHUDMaskType)maskType {
SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType;
[self setDefaultMaskType:maskType];
[self showProgress:progress];
// 顯示完之后回到默認的遮罩樣式
[self setDefaultMaskType:existingMaskType];
}
我簡單分析了一下不通過參數來傳遞遮罩樣式的原因應該是為了每次顯示完之后保證下一次遮罩的樣式依然是默認的樣式。可以看到每次調用完show**
之后都會把mask恢復到默認值。
+ (void)showImage:(UIImage*)image status:(NSString*)status
這個方法是會自動消失show**
最終會調用的方法,比如
+ (void)showInfoWithStatus:(NSString*)status;
+ (void)showInfoWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use showInfoWithStatus: and setDefaultMaskType: instead.")));
+ (void)showSuccessWithStatus:(NSString*)status;
+ (void)showSuccessWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use showSuccessWithStatus: and setDefaultMaskType: instead.")));
+ (void)showErrorWithStatus:(NSString*)status;
+ (void)showErrorWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use showErrorWithStatus: and setDefaultMaskType: instead.")));
// shows a image + status, use 28x28 white PNGs
+ (void)showImage:(UIImage*)image status:(NSString*)status;
+ (void)showImage:(UIImage*)image status:(NSString*)status maskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use showImage:status: and setDefaultMaskType: instead.")));
UIAccessibility
UIAccessibility協議用于讓外界程序了解到自己身的執情情況。Accessibility是一個交互協議,基于查詢<->應答,通知<->監聽模型的協議。外部程序通過查詢來獲取APP應答,從而了解程序。另外通過監聽來自APP的消息,來通知用戶當前狀態。
- 1.常用的協議與元素包括:
UIAccessibility, protocol,核心協議。
UIAccessibilityAction,protocol,添加行為的協議。 UIAccessibilityElement, class。
UIAccessibilityContainer,protocol,容器協議。
- 2.常用函數 UIAccessibilityPostNotification。
可以看到SVProgressHUD支持UIAccessibility
// Accessibility support
self.accessibilityIdentifier = @"SVProgressHUD";
self.accessibilityLabel = @"SVProgressHUD";
self.isAccessibilityElement = YES;
看一下官方介紹
/*
UIAccessibility
UIAccessibility is implemented on all standard UIKit views and controls so
that assistive applications can present them to users with disabilities.
Custom items in a user interface should override aspects of UIAccessibility
to supply details where the default value is incomplete.
For example, a UIImageView subclass may need to override accessibilityLabel,
but it does not need to override accessibilityFrame.
A completely custom subclass of UIView might need to override all of the
UIAccessibility methods except accessibilityFrame.
*/
showProgress:(float)progress status:(NSString*)status
我們都知道關于UI的操作都需要放在主線程中。一般會通過GCD的方式如下:
dispatch_async(dispatch_get_main_queue(), ^{
});
但是SVProgressHUD里面用的是NSOperation來實現的,
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
}];
這也給了我們另外一種方式回到主線程。??
strong & weak
__weak SVProgressHUD *weakSelf = self;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
__strong SVProgressHUD *strongSelf = weakSelf;
if(strongSelf){
// Update / Check view hierachy to ensure the HUD is visible
[strongSelf updateViewHierachy];
......
上面的代碼是從源碼中摘的,可以看到為了防止循環引用在block外面用了__weak SVProgressHUD *weakSelf = self;
而在block里面用了__strong SVProgressHUD *strongSelf = weakSelf;
最開始了解這種用法是從AFNetWorking源碼中看到了。為了保證在執行block的時候weakSelf還存在(因為可能會延遲調用),所以需要在block里面用__strong在修飾一次weakSelf.
視圖顯示的邏輯
為了更好地說明問題,我直接在源碼中加注釋方便說明。
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
__strong SVProgressHUD *strongSelf = weakSelf;
// 邏輯嚴謹,需要判斷是否存在
if(strongSelf){
// 更新并且檢查視圖層次確保SVProgressHUD可見
[strongSelf updateViewHierachy];
// 重置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上,并且設置當前及大怒
[strongSelf.hudView addSubview:strongSelf.ringView];
[strongSelf.hudView addSubview:strongSelf.backgroundRingView];
strongSelf.ringView.strokeEnd = progress;
// 更新activityCount
if(progress == 0) {
strongSelf.activityCount++;
}
} else {
// 防止上次為進度的樣式導致重疊
[strongSelf cancelRingLayerAnimation];
// 增加無限旋轉視圖到hudview上
[strongSelf.hudView addSubview:strongSelf.indefiniteAnimatedView];
if([strongSelf.indefiniteAnimatedView respondsToSelector:@selector(startAnimating)]) {
[(id)strongSelf.indefiniteAnimatedView startAnimating];
}
strongSelf.activityCount++;
}
// 顯示提示的文字信息
[strongSelf showStatus:status];
}
}];
大致的思路理清了。把顯示文字的邏輯和顯示進度與旋轉的路基分開來實現的。因為顯示文字的邏輯是公有的。
上面代碼中需要注意的是有一個更新視圖層次的方法[strongSelf updateViewHierachy]
.因為視圖層次很有可能在運行的時候被改變,比如通過runtime。所以每次都需要更新一下視圖層次,保證能夠顯示出來。
更新提示文字showStatus:(NSString*)status
還是根據代碼來看吧
- (void)showStatus:(NSString*)status {
// 更新frame及位置,因為frame是更加status來確定的而postion是根據參數控制的。
[self updateHUDFrame];
[self positionHUD:nil];
// 更新 accesibilty 和是否可以點擊
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;
}
self.overlayView.backgroundColor = [UIColor clearColor];
// 根據alpha值判斷是是否可見
if(self.alpha != 1.0f || self.hudView.alpha != 1.0f) {
// 如果之前不可見則發出SVProgressHUDWillAppearNotification通知,告訴馬上顯示
[[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDWillAppearNotification
object:self
userInfo:[self notificationUserInfo]];
// 縮放效果
self.hudView.transform = CGAffineTransformScale(self.hudView.transform, 1.3, 1.3);
// 處理初始值處理iOS7及以上不會響應透明度改變的UIToolbar
self.alpha = 0.0f;
self.hudView.alpha = 0.0f;
// 定義動畫block及完成動畫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) {
/// 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) {
// 如果設置了動畫時間則進行動畫效果
[UIView animateWithDuration:self.fadeInAnimationDuration
delay:0
options:(UIViewAnimationOptions) (UIViewAnimationOptionAllowUserInteraction | UIViewAnimationCurveEaseOut | UIViewAnimationOptionBeginFromCurrentState)
animations:^{
animationsBlock();
} completion:^(BOOL finished) {
completionBlock();
}];
} else {
// 未設置動畫時間
animationsBlock();
completionBlock();
}
// 完成了更新視圖層次,視圖的frame以及視圖的各種屬性之后,告訴系統稍微進行重繪
[self setNeedsDisplay];
}
}
以后再寫吧.....
真的沒想到自己把看懂的東西一個個寫出來是這么的麻煩,讓自己寫出來進行分享的時候真的好費時間。這篇文字自己也是抽空一點一點拼湊起來的。
自己也一直想做一個源碼分析的系列,前不久看到了github上已經有人有類似的項目。有些觸動,有時候自己想法很多而實際動手去做的時間卻很少。總的說來自己總結出,分析開源代碼不難,難的是如果寫好分析文章。
可能分析得有些粗糙。不過也是自己一個字一個字寫出來的。堅持下去吧!!