SVProgressHUD源碼解讀(2.0.3)

SVProgressHUDiOS開發(fā)中比較常用的一個(gè)三方庫,用來在執(zhí)行耗時(shí)操作或者指示用戶操作結(jié)果的場合,由于使用簡單,功能豐富,交互友好,被廣泛應(yīng)用。本文從源碼的角度,解讀一下實(shí)現(xiàn)的過程,希望能起到拋磚引玉的作用。

一. 效果預(yù)覽

  • SVPIndefiniteAnimatedView
無限循環(huán)
  • SVProgressAnimatedView
單次滾動(dòng)
  • 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)畫。其中,核心部分是利用layermask屬性實(shí)現(xiàn)遮罩功能,而mask的實(shí)現(xiàn)方法是顯示顯示bounds的非透明部分,實(shí)例圖如下:

mask效果
  • SVProgressAnimatedView 類

這個(gè)類提供一個(gè)畫圓環(huán)的視圖,通過不斷改變layerstrokeEnd的值,實(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

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

推薦閱讀更多精彩內(nèi)容