SVProgressHUD源碼解讀(2.0.3)

SVProgressHUDiOS開發(fā)中比較常用的一個(gè)三方庫(kù),用來在執(zhí)行耗時(shí)操作或者指示用戶操作結(jié)果的場(chǎng)合,由于使用簡(jiǎn)單,功能豐富,交互友好,被廣泛應(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采用單例模式,簡(jiǎn)化代碼維護(hù);同時(shí),根據(jù)SVProgressHUD的層級(jí)結(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://m.itdecent.cn/p/a08d4597cf24

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容