SVProgressHUD源碼閱讀筆記

SVProgressHUD 是iOS開發(fā)中比較常用的一個第三方庫,之前好幾個項目都用到了這個庫,但是一個沒有去看它到底是怎么實現(xiàn)的,現(xiàn)在項目基本完成了,深入了解了下SVProgressHUD的源碼,下面是我的理解,如果有什么錯誤的地方,望指正。

實現(xiàn)的效果

  • SVIndefiniteAnimatedView
無限循環(huán)
  • SVProgressAnimatedView
單次滾動
  • SVRadialGradientLayer
漸變視圖

類文件分析

整個SVProgressHUD包含4個類和一個bundle文件,除SVProgressHUD類之外,其他都是用來修飾顯示效果的。

SVProgressHUD

用來顯示提示框,通過類方法來設(shè)置屬性和顯示/消失提示框。

1. HUD樣式

typedef NS_ENUM(NSInteger, SVProgressHUDStyle) {
    SVProgressHUDStyleLight,        // 白色背景
    SVProgressHUDStyleDark,         // 黑色背景
    SVProgressHUDStyleCustom        // 自定義
};

2. HUD遮罩層樣式

typedef NS_ENUM(NSUInteger, SVProgressHUDMaskType) {
    SVProgressHUDMaskTypeNone = 1,  // 允許用戶交互,遮罩層透明
    SVProgressHUDMaskTypeClear,     // 不允許用戶交互,遮罩層透明
    SVProgressHUDMaskTypeBlack,     // 不允許用戶交互,遮罩層黑色
    SVProgressHUDMaskTypeGradient,  // 不允許用戶交互,遮罩層漸變色
    SVProgressHUDMaskTypeCustom     // 不允許用戶交互,遮罩層自定義顏色
};

3. HUD動畫類型

typedef NS_ENUM(NSUInteger, SVProgressHUDAnimationType) {
    SVProgressHUDAnimationTypeFlat,     // indefinite animated
    SVProgressHUDAnimationTypeNative    // iOS 系統(tǒng)提供的 UIActivityIndicatorView
};

4. 常用屬性

UI_APPEARANCE_SELECTOR 可以統(tǒng)一設(shè)置屬性。

@property (assign, nonatomic) CGFloat ringThickness UI_APPEARANCE_SELECTOR;                 // 圓環(huán)的寬度 默認(rèn)2 pt
@property (assign, nonatomic) CGFloat ringRadius UI_APPEARANCE_SELECTOR;                    // 圓環(huán)的半徑 默認(rèn)18 pt
@property (assign, nonatomic) CGFloat ringNoTextRadius UI_APPEARANCE_SELECTOR;              // 沒有text的時候圓環(huán)的半徑 默認(rèn)24 pt
@property (assign, nonatomic) CGFloat cornerRadius UI_APPEARANCE_SELECTOR;                  // HUD的圓角 默認(rèn)14 pt
@property (strong, nonatomic, nonnull) UIFont *font UI_APPEARANCE_SELECTOR;                 // 字體,默認(rèn)使用 [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline]

5. 圖片類型

默認(rèn)是從bundle文件中讀取的

@property (strong, nonatomic, nonnull) UIImage *infoImage UI_APPEARANCE_SELECTOR;          // 顯示信息的圖片
@property (strong, nonatomic, nonnull) UIImage *successImage UI_APPEARANCE_SELECTOR;        // 成功圖片
@property (strong, nonatomic, nonnull) UIImage *errorImage UI_APPEARANCE_SELECTOR;          // 錯誤圖片

6. 常用方法介紹

  • 無限循環(huán)狀態(tài)顯示,不會自動消失,需要主動調(diào)用dismiss方法
+ (void)show;
+ (void)showWithStatus:(nullable NSString*)status;

+ (void)dismiss;
+ (void)dismissWithCompletion:(nullable SVProgressHUDDismissCompletion)completion;
+ (void)dismissWithDelay:(NSTimeInterval)delay;
+ (void)dismissWithDelay:(NSTimeInterval)delay completion:(nullable SVProgressHUDDismissCompletion)completion;

  • 進度條狀態(tài)顯示
+ (void)showProgress:(float)progress;
+ (void)showProgress:(float)progress status:(nullable NSString*)status;

  • 圖片狀態(tài)顯示
+ (void)showInfoWithStatus:(nullable NSString*)status;
+ (void)showSuccessWithStatus:(nullable NSString*)status;
+ (void)showErrorWithStatus:(nullable NSString*)status;

+ (void)showImage:(nonnull UIImage*)image status:(nullable NSString*)status;

  • hud距離中心點的偏移量
+ (void)setOffsetFromCenter:(UIOffset)offset;
+ (void)resetOffsetFromCenter;

7. 通知

監(jiān)聽用戶觸摸事件,HUD顯示和消失事件的通知

extern NSString * _Nonnull const SVProgressHUDDidReceiveTouchEventNotification;
extern NSString * _Nonnull const SVProgressHUDDidTouchDownInsideNotification;
extern NSString * _Nonnull const SVProgressHUDWillDisappearNotification;
extern NSString * _Nonnull const SVProgressHUDDidDisappearNotification;
extern NSString * _Nonnull const SVProgressHUDWillAppearNotification;
extern NSString * _Nonnull const SVProgressHUDDidAppearNotification;

8. 顯示流程

SVProgressHUD 全部采用類方法,使用單例模式初始化HUD對象,層次結(jié)構(gòu)為
UIWindow -> UIControl -> SVProgressHUD -> HUDView -> AnimatedView

處理顯示邏輯的方法,顯示的時候要回到主線程中去操作,每次顯示之前都會將之前顯示的HUD移除掉,并移除之前的動畫,使用CATransaction避免被干擾。

- (void)showProgress:(float)progress status:(NSString*)status
- (void)showImage:(UIImage*)image status:(NSString*)status duration:(NSTimeInterval)duration
- (void)showProgress:(float)progress status:(NSString*)status {
    __weak SVProgressHUD *weakSelf = self;
    // 這里并沒有采用GCD的方式 而是使用NSOperationQueue 回到主隊列執(zhí)行operation
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        __strong SVProgressHUD *strongSelf = weakSelf;
        if(strongSelf){
            // Stop timer
            strongSelf.fadeOutTimer = nil;
            strongSelf.graceTimer = nil;
            
            // 更新視圖層級 使得HUD可以始終顯示在最外層
            // Update / Check view hierarchy to ensure the HUD is visible
            [strongSelf updateViewHierarchy];
            
            // Reset imageView and fadeout timer if an image is currently displayed
            strongSelf.imageView.hidden = YES;
            strongSelf.imageView.image = nil;
            
            // Update text and set progress to the given value
            strongSelf.statusLabel.hidden = status.length == 0;
            strongSelf.statusLabel.text = status;
            strongSelf.progress = progress;
            
            // progress >= 0時 顯示進度條動畫ring
            // Choose the "right" indicator depending on the progress
            if(progress >= 0) {
                // Cancel the indefiniteAnimatedView, then show the ringLayer
                [strongSelf cancelIndefiniteAnimatedViewAnimation];
                
                // Add ring to HUD
                // ringView 沒有superView 即ringView沒有被添加進view中
                if(!strongSelf.ringView.superview){
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
                    [strongSelf.hudView.contentView addSubview:strongSelf.ringView];
#else
                    [strongSelf.hudView addSubview:strongSelf.ringView];
#endif
                }
                // backgroundRingView ringView 同一種類型
                if(!strongSelf.backgroundRingView.superview){
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
                    [strongSelf.hudView.contentView addSubview:strongSelf.backgroundRingView];
#else
                    [strongSelf.hudView addSubview:strongSelf.backgroundRingView];
#endif
                }
                
                // Set progress animated
                // CATransaction 動畫的事務(wù)性 這里是顯式事物
                [CATransaction begin];
                // 不顯示動畫的過程
                [CATransaction setDisableActions:YES];
                strongSelf.ringView.strokeEnd = progress;
                [CATransaction commit];
            } else {
                // Cancel the ringLayer animation, then show the indefiniteAnimatedView
                // 取消progress的動畫,顯示無限循環(huán)的那個動畫
                [strongSelf cancelRingLayerAnimation];
                
                // Add indefiniteAnimatedView to HUD
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
                [strongSelf.hudView.contentView addSubview:strongSelf.indefiniteAnimatedView];
#else
                [strongSelf.hudView addSubview:strongSelf.indefiniteAnimatedView];
#endif
                // 開始執(zhí)行動畫
                if([strongSelf.indefiniteAnimatedView respondsToSelector:@selector(startAnimating)]) {
                    [(id)strongSelf.indefiniteAnimatedView startAnimating];
                }
            }
            
            // Fade in delayed if a grace time is set
            if (self.graceTimeInterval > 0.0 && self.backgroundView.alpha == 0.0f) {
                strongSelf.graceTimer = [NSTimer timerWithTimeInterval:self.graceTimeInterval target:strongSelf selector:@selector(fadeIn:) userInfo:nil repeats:NO];
                // 添加到NSRunLoopCommonModes中 避免其他的RunLoop干擾
                [[NSRunLoop mainRunLoop] addTimer:strongSelf.graceTimer forMode:NSRunLoopCommonModes];
            } else {
                [strongSelf fadeIn:nil];
            }
            
            // 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)fadeIn:(id)data {
    // Update the HUDs frame to the new content and position HUD
    // 更新HUD的frame
    [self updateHUDFrame];
    // 更新HUD的位置
    [self positionHUD:nil];
    
    // Update accessibility as well as user interaction
    if(self.defaultMaskType != SVProgressHUDMaskTypeNone) {
        // 不擁護用戶交互 controlView 繼承自UIControl
        self.controlView.userInteractionEnabled = YES;
        self.accessibilityLabel = self.statusLabel.text ?: NSLocalizedString(@"Loading", nil);
        self.isAccessibilityElement = YES;
    } else {
        // 用戶是可以交互的 可以點擊除顯示HUD之外的區(qū)域
        self.controlView.userInteractionEnabled = NO;
        self.hudView.accessibilityLabel = self.statusLabel.text ?: NSLocalizedString(@"Loading", nil);
        self.hudView.isAccessibilityElement = YES;
    }
    
    // Get duration
    // 獲取顯示時間
    id duration = [data isKindOfClass:[NSTimer class]] ? ((NSTimer *)data).userInfo : data;
    
    // Show if not already visible
    if(self.backgroundView.alpha != 1.0f) { // 開始要顯示
        // Post notification to inform user
        // 通知用戶將要顯示HUD了
        [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDWillAppearNotification
                                                            object:self
                                                          userInfo:[self notificationUserInfo]];
        
        // Shrink HUD to to make a nice appear / pop up animation
        /*
         CGAffineTransform CGAffineTransformScale(CGAffineTransform t,
         CGFloat sx, CGFloat sy)
         t 要進行變換的矩陣
         sx x方向上的縮放倍數(shù)
         sy y方向上的縮放倍數(shù)
         
         縮小2/3
         */
        self.hudView.transform = self.hudView.transform = CGAffineTransformScale(self.hudView.transform, 1/1.5f, 1/1.5f);
        
        // 執(zhí)行完動畫后的回調(diào) 這里寫成block的原因 可以在方法里調(diào)用方法
        __block void (^animationsBlock)(void) = ^{
            // Zoom HUD a little to make a nice appear / pop up animation
            self.hudView.transform = CGAffineTransformIdentity;
            
            // Fade in all effects (colors, blur, etc.)
            [self fadeInEffects];
        };
        
        // 顯示完成后的回調(diào)
        __block void (^completionBlock)(void) = ^{
            // Check if we really achieved to show the HUD (<=> alpha)
            // and the change of these values has not been cancelled in between e.g. due to a dismissal
            if(self.backgroundView.alpha == 1.0f){ // 顯示完成
                // 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]];
                
                // Update accessibility
                UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil);
                UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, self.statusLabel.text);
                
                // Dismiss automatically if a duration was passed as userInfo. We start a timer
                // which then will call dismiss after the predefined duration
                if(duration){
                    // 顯示完成后使用計時器調(diào)用消失的方法
                    self.fadeOutTimer = [NSTimer timerWithTimeInterval:[(NSNumber *)duration doubleValue] target:self selector:@selector(dismiss) userInfo:nil repeats:NO];
                    [[NSRunLoop mainRunLoop] addTimer:self.fadeOutTimer forMode:NSRunLoopCommonModes];
                }
            }
        };
        
        // Animate appearance
        if (self.fadeInAnimationDuration > 0) {
            // Animate appearance
            [UIView animateWithDuration:self.fadeInAnimationDuration
                                  delay:0
                                options:(UIViewAnimationOptions) (UIViewAnimationOptionAllowUserInteraction | UIViewAnimationCurveEaseIn | UIViewAnimationOptionBeginFromCurrentState)
                             animations:^{
                                 animationsBlock();
                             } completion:^(BOOL finished) {
                                 completionBlock();
                             }];
        } else {
            animationsBlock();
            completionBlock();
        }
        
        // Inform iOS to redraw the view hierarchy
        // 告訴系統(tǒng)更新視圖層級
        [self setNeedsDisplay];
    } else { // 如果已經(jīng)顯示
        // Update accessibility
        UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil);
        UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, self.statusLabel.text);
        
        // Dismiss automatically if a duration was passed as userInfo. We start a timer
        // which then will call dismiss after the predefined duration
        if(duration){
            self.fadeOutTimer = [NSTimer timerWithTimeInterval:[(NSNumber *)duration doubleValue] target:self selector:@selector(dismiss) userInfo:nil repeats:NO];
            [[NSRunLoop mainRunLoop] addTimer:self.fadeOutTimer forMode:NSRunLoopCommonModes];
        }
    }
}

處理消失邏輯的方法,同樣需要到主線程中操作。

- (void)dismissWithDelay:(NSTimeInterval)delay completion:(SVProgressHUDDismissCompletion)completion
- (void)dismissWithDelay:(NSTimeInterval)delay completion:(SVProgressHUDDismissCompletion)completion {
    __weak SVProgressHUD *weakSelf = self;
    // 同show方法
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        __strong SVProgressHUD *strongSelf = weakSelf;
        if(strongSelf){
            // Stop timer
            strongSelf.graceTimer = nil;
            
            // Post notification to inform user
            [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDWillDisappearNotification
                                                                object:nil
                                                              userInfo:[strongSelf notificationUserInfo]];
            
            __block void (^animationsBlock)(void) = ^{
                // Shrink HUD a little to make a nice disappear animation
                strongSelf.hudView.transform = CGAffineTransformScale(strongSelf.hudView.transform, 1/1.3f, 1/1.3f);
                
                // Fade out all effects (colors, blur, etc.)
                [strongSelf fadeOutEffects];
            };
            
            __block void (^completionBlock)(void) = ^{
                // Check if we really achieved to dismiss the HUD (<=> alpha values are applied)
                // and the change of these values has not been cancelled in between e.g. due to a new show
                if(self.backgroundView.alpha == 0.0f){
                    // Clean up view hierarchy (overlays)
                    [strongSelf.controlView removeFromSuperview];
                    [strongSelf.backgroundView removeFromSuperview];
                    [strongSelf.hudView removeFromSuperview];
                    [strongSelf removeFromSuperview];
                    
                    // Reset progress and cancel any running animation
                    strongSelf.progress = SVProgressHUDUndefinedProgress;
                    [strongSelf cancelRingLayerAnimation];
                    [strongSelf cancelIndefiniteAnimatedViewAnimation];
                    
                    // Remove observer <=> we do not have to handle orientation changes etc.
                    [[NSNotificationCenter defaultCenter] removeObserver:strongSelf];
                    
                    // Post notification to inform user
                    [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDDidDisappearNotification
                                                                        object:strongSelf
                                                                      userInfo:[strongSelf notificationUserInfo]];
                    
                    // Tell the rootViewController to update the StatusBar appearance
#if !defined(SV_APP_EXTENSIONS) && TARGET_OS_IOS
                    // 更新狀態(tài)欄
                    UIViewController *rootController = [[UIApplication sharedApplication] keyWindow].rootViewController;
                    [rootController setNeedsStatusBarAppearanceUpdate];
#endif
                    
                    // Run an (optional) completionHandler
                    if (completion) {
                        completion();
                    }
                }
            };
            
            // UIViewAnimationOptionBeginFromCurrentState AND a delay doesn't always work as expected
            // When UIViewAnimationOptionBeginFromCurrentState is set, animateWithDuration: evaluates the current
            // values to check if an animation is necessary. The evaluation happens at function call time and not
            // after the delay => the animation is sometimes skipped. Therefore we delay using dispatch_after.
            
            dispatch_time_t dipatchTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC));
            dispatch_after(dipatchTime, dispatch_get_main_queue(), ^{
                if (strongSelf.fadeOutAnimationDuration > 0) {
                    // Animate appearance
                    [UIView animateWithDuration:strongSelf.fadeOutAnimationDuration
                                          delay:0
                                        options:(UIViewAnimationOptions) (UIViewAnimationOptionAllowUserInteraction | UIViewAnimationCurveEaseOut | UIViewAnimationOptionBeginFromCurrentState)
                                     animations:^{
                                         animationsBlock();
                                     } completion:^(BOOL finished) {
                                         completionBlock();
                                     }];
                } else {
                    animationsBlock();
                    completionBlock();
                }
            });
            
            // Inform iOS to redraw the view hierarchy
            // 告訴系統(tǒng)更新視圖層級
            [strongSelf setNeedsDisplay];
        }
    }];
}

SVIndefiniteAnimatedView

這個類提供了無限旋轉(zhuǎn)動畫,可以單獨拿出來使用,實現(xiàn)思路是
初始化兩個layer對象,indefiniteAnimatedLayer和maskLayer,maskLayer有一張圖片,設(shè)置indefiniteAnimatedLayer的mask為maskLayer,這樣就得到一張漸變的layer。分別設(shè)置layer和layer.mask的動畫達到效果。示例圖:

mask效果

SVProgressAnimatedView

這個類提供環(huán)形加載效果,通過不斷修改strokeEnd的值,實現(xiàn)了進度的顯示。

- (void)setStrokeEnd:(CGFloat)strokeEnd {
    _strokeEnd = strokeEnd;
    _ringAnimatedLayer.strokeEnd = _strokeEnd;
}

SVRadialGradientLayer

繼承自CALayer,使用CGContextDrawRadialGradient方法畫漸變層。

以上,就是暫時對SVProgressHUD的理解,理解不夠的地方,歡迎交流。

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

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

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,429評論 4 61
  • 知曉我們的美麗,源自他們的皺紋,愛他們被風(fēng)塵刻畫的樣子 ...
    苗弍吖閱讀 335評論 0 0
  • 白茴早就記不清自己到底多少次自殺 但這一次,她再也沒有醒來 她睡著年少無知的薄涼 安然無事 ...
    葉清然閱讀 731評論 6 4
  • u 早起:晨讀,英語復(fù)習(xí) u 中午:吃餃子嘍,好久沒有好好給孩子做飯了,終于可以改善一下 u 吃青蛙,番茄鐘:高效...
    翰悅寧心閱讀 158評論 0 0

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