SVProgressHUD(2.0.3)原來是這樣

有段時間沒有寫了。這個周末抽空簡單整理了一下關于自己對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上已經有人有類似的項目。有些觸動,有時候自己想法很多而實際動手去做的時間卻很少。總的說來自己總結出,分析開源代碼不難,難的是如果寫好分析文章。

可能分析得有些粗糙。不過也是自己一個字一個字寫出來的。堅持下去吧!!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,698評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,202評論 3 426
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,742評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,580評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,297評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,688評論 1 327
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,693評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,875評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,438評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,183評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,384評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,931評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,612評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,022評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,297評論 1 292
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,093評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,330評論 2 377

推薦閱讀更多精彩內容

  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結起來就是把...
    Dove_iOS閱讀 27,197評論 30 471
  • 轉至元數據結尾創建: 董瀟偉,最新修改于: 十二月 23, 2016 轉至元數據起始第一章:isa和Class一....
    40c0490e5268閱讀 1,753評論 0 9
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,816評論 18 139
  • 從小到大明明很脆弱的內心卻總要故裝強大,明明也需要別人的幫助卻總是告訴自己忍忍就過了,所以無論什么時候只要他人能找...
    小鄧同學閱讀 439評論 0 0
  • 人呢總有忽如其來的奇思妙想,似乎等了這么久就是在尋找一個可以如此暢所欲言,直抒己意的平臺。不需要咬文嚼字,只求一個...
    踏浪的小腳丫閱讀 283評論 0 0