SVProgressHUD源碼解讀(2.0.3)

来源:http://www.cnblogs.com/fishbay/archive/2017/07/17/7196987.html
-Advertisement-
Play Games

`SVProgressHUD iOS`開發中比較常用的一個三方庫,用來在執行耗時操作或者指示用戶操作結果的場合,由於使用簡單,功能豐富,交互友好,被廣泛應用。本文從源碼的角度,解讀一下實現的過程,希望能起到拋磚引玉的作用。 一. 效果預覽 1. SVPIndefiniteAnimatedView 2 ...


SVProgressHUDiOS開發中比較常用的一個三方庫,用來在執行耗時操作或者指示用戶操作結果的場合,由於使用簡單,功能豐富,交互友好,被廣泛應用。本文從源碼的角度,解讀一下實現的過程,希望能起到拋磚引玉的作用。

一. 效果預覽

1. SVPIndefiniteAnimatedView

無限迴圈

2. SVProgressAnimatedView

單次滾動

3. SVRadialGradientLayer

漸變視圖

二. 類分析

1. SVProgressHUD

這是SVProgressHUD顯示提示框的類,提供類方法和屬性來進行不同的設置。

** HUD提示框背景

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

** 遮罩層背景

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

** 無限迴圈的顯示類型

typedef NS_ENUM(NSUInteger, SVProgressHUDAnimationType) {
    SVProgressHUDAnimationTypeFlat, // SVPIndefiniteAnimatedView
    SVProgressHUDAnimationTypeNative // 系統的UIActivityIndicatorView
};

**常用屬性介紹

hud最小尺寸,預設是(100,100)

@property (assign, nonatomic) CGSize minimumSize UI_APPEARANCE_SELECTOR;

圓環厚度,預設是2px

@property (assign, nonatomic) CGFloat ringThickness UI_APPEARANCE_SELECTOR;

圓環半徑,預設是18px

@property (assign, nonatomic) CGFloat ringRadius UI_APPEARANCE_SELECTOR;

提示語字體,預設是14px

@property (strong, nonatomic) UIFont *font UI_APPEARANCE_SELECTOR;

Image提示框顯示時間,預設是5s

@property (assign, nonatomic) NSTimeInterval minimumDismissTimeInterval;

** 常用方法介紹

  • 無限迴圈狀態顯示,不會自動小時,需主動調用dismiss方法
+ (void)show;
+ (void)showWithStatus:(NSString*)status;

+ (void)dismiss;
+ (void)dismissWithCompletion:(SVProgressHUDDismissCompletion)completion;
+ (void)dismissWithDelay:(NSTimeInterval)delay;
+ (void)dismissWithDelay:(NSTimeInterval)delay completion:(SVProgressHUDDismissCompletion)completion;
  • 進度條狀態顯示
+ (void)showProgress:(float)progress;
+ (void)showProgress:(float)progress status:(NSString*)status;
  • 圖片狀態顯示
+ (void)showInfoWithStatus:(NSString*)status;
+ (void)showSuccessWithStatus:(NSString*)status;
+ (void)showErrorWithStatus:(NSString*)status;
  • hud距離中心點的偏移量
+ (void)setOffsetFromCenter:(UIOffset)offset;
+ (void)resetOffsetFromCenter;

** 通知

通過監聽不同的通知事件,可以獲取hud的狀態

extern NSString * const SVProgressHUDWillDisappearNotification;
extern NSString * const SVProgressHUDDidDisappearNotification;
extern NSString * const SVProgressHUDWillAppearNotification;
extern NSString * const SVProgressHUDDidAppearNotification;

** hud顯示流程

SVProgressHUD採用單例模式,簡化代碼維護;同時,根據SVProgressHUD的層級結構可以看出,從底層到頂層依次是:UIControl (overlayView) -> SVProgressHUD -> UIView (hudView) -> UIVisualEffectView -> AnimatedView (具體動畫視圖) 。

  • -(void)showStatus:(NSString*)status, 這是顯示無限迴圈狀態的提示框,可以添加文字進一步詳細補充。其中,SVProgressHUD採用圖形和文字分離的模式,方面文字視圖的復用。所有,顯示文字的視圖,最終都會調用下麵的方法。
- (void)showStatus:(NSString*)status {
    // 更新frame及位置,因為frame是更加status來確定的而postion是根據參數控制的。
    [self updateHUDFrame];
    [self positionHUD:nil];
    
    // 更新 accesibilty 和是否可以點擊
    if(self.defaultMaskType != SVProgressHUDMaskTypeNone) {
        self.accessibilityLabel = status;
        self.isAccessibilityElement = YES;
    } else {
        self.hudView.accessibilityLabel = status;
        self.hudView.isAccessibilityElement = YES;
    }
    
    // Show if not already visible
    // Checking one alpha value is sufficient as they are all the same
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
    if(self.hudView.contentView.alpha != 1.0f){
#else
     根據alpha值判斷是是否可見
    if(self.hudView.alpha != 1.0f){
#endif
        // 如果之前不可見則發出SVProgressHUDWillAppearNotification通知,告訴馬上顯示
        [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDWillAppearNotification
                                                            object:self
                                                          userInfo:[self notificationUserInfo]];
        
        //縮放效果
        self.hudView.transform = CGAffineTransformScale(self.hudView.transform, 1.3, 1.3);
        
        // Activate blur on view before animation on older iOS versions,
        // as we cannot animate this property and use alpha values instead
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
        bool greateriOS9 = kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_9_0;
        if (self.defaultStyle != SVProgressHUDStyleCustom && !greateriOS9) {
            [self addBlur];
            
            // Update alpha
            self.hudView.contentView.alpha = 1.0f;
        }
#endif
        
        
        // Define blocks
        __block void (^animationsBlock)(void) = ^{
            // Shrink HUD to finish pop up animation
            self.hudView.transform = CGAffineTransformScale(self.hudView.transform, 1/1.3f, 1/1.3f);
            
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
            if(self.defaultStyle != SVProgressHUDStyleCustom && greateriOS9){
                // Fade in blur effect
                [self addBlur];
                
                // Update alpha
                self.hudView.contentView.alpha = 1.0f;
            } else {
                // This gives a warning on iOS 8, however it works, see #703
                self.hudView.alpha = 1.0f;
            }
#else
            self.hudView.alpha = 1.0f;
            self.hudView.contentView.alpha = 1.0f;
#endif
            self.backgroundView.alpha = 1.0f;
        };
        
        __block void (^completionBlock)(void) = ^{
            // 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
            // Checking one alpha value is sufficient as they are all the same
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
            if(self.hudView.contentView.alpha == 1.0f){
#else
            if(self.hudView.alpha == 1.0f){
#endif
                // 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]];
            }
            
            // 更新 accesibilty
            UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil);
            UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, status);
        };
        
        if (self.fadeInAnimationDuration > 0) {
            // 如果設置了動畫時間則進行動畫效果
            [UIView animateWithDuration:self.fadeInAnimationDuration
                                  delay:0
                                options:(UIViewAnimationOptions) (UIViewAnimationOptionAllowUserInteraction | UIViewAnimationCurveEaseIn | UIViewAnimationOptionBeginFromCurrentState)
                             animations:^{
                                 animationsBlock();
                             } completion:^(BOOL finished) {
                                 completionBlock();
                             }];
        } else {
            animationsBlock();
            completionBlock();
        }
        
        // 完成了更新視圖層次,視圖的frame以及視圖的各種屬性之後,告訴系統稍微進行重繪
        [self setNeedsDisplay];
    }
}
  • -(void)showProgress:(float)progress status:(NSString*)status,這是顯示單次滾動效果的提示框,每次顯示視圖前,都會取消其它視圖,防止上次顯示不同視圖產生的干擾。其中,在設置strokeEnd時,使用事物類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和消失時間。防止之前調用過,使用上次存在的樣式設置
            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上,並且設置當前進度值
                if(!strongSelf.ringView.superview){
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
                    [strongSelf.hudVibrancyView.contentView addSubview:strongSelf.ringView];
#else
                    [strongSelf.hudView addSubview:strongSelf.ringView];
#endif
                }
                if(!strongSelf.backgroundRingView.superview){
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
                    [strongSelf.hudVibrancyView.contentView addSubview:strongSelf.backgroundRingView];
#else
                    [strongSelf.hudView addSubview:strongSelf.backgroundRingView];
#endif
                }
                
                // Set progress animated
                [CATransaction begin];
                [CATransaction setDisableActions:YES];
                strongSelf.ringView.strokeEnd = progress;
                [CATransaction commit];
                
                // 更新activityCount
                if(progress == 0) {
                    strongSelf.activityCount++;
                }
            } else {
                // 防止上次為進度的樣式導致重疊
                [strongSelf cancelRingLayerAnimation];
                
                // 增加無限旋轉視圖到hudview上
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
                [strongSelf.hudVibrancyView.contentView addSubview:strongSelf.indefiniteAnimatedView];
#else
                [strongSelf.hudView  addSubview:strongSelf.indefiniteAnimatedView];
#endif
                if([strongSelf.indefiniteAnimatedView respondsToSelector:@selector(startAnimating)]) {
                    [(id)strongSelf.indefiniteAnimatedView startAnimating];
                }
                
                // Update the activity count
                strongSelf.activityCount++;
            }
            
            // 顯示提示的文字信息
            [strongSelf showStatus:status];
            
            // 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)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,並取消其它動畫
            strongSelf.progress = SVProgressHUDUndefinedProgress;
            [strongSelf cancelRingLayerAnimation];
            [strongSelf cancelIndefiniteAnimatedViewAnimation];
            
            // 更新imageView
            UIColor *tintColor = strongSelf.foregroundColorForStyle;
            UIImage *tintedImage = image;
            if (image.renderingMode != UIImageRenderingModeAlwaysTemplate) {
                tintedImage = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
            }
            strongSelf.imageView.tintColor = tintColor;
            strongSelf.imageView.image = tintedImage;
            strongSelf.imageView.hidden = NO;
            
            // 更新文字
            strongSelf.statusLabel.text = status;
            
            // 顯示文字視圖
            [strongSelf showStatus:status];
            
            // An image will be dismissed automatically. Therefore, we start a timer
            // which then will call dismiss after the predefined duration
            // 添加定時消失timer
            strongSelf.fadeOutTimer = [NSTimer timerWithTimeInterval:duration target:strongSelf selector:@selector(dismiss) userInfo:nil repeats:NO];
            [[NSRunLoop mainRunLoop] addTimer:strongSelf.fadeOutTimer forMode:NSRunLoopCommonModes];
        }
    }];
}

**其它方法

  • 設置hud位置的方法

- (void)positionHUD:(NSNotification*)notification

  • 調整hud尺寸的方法

- (void)updateHUDFrame

  • 更新模糊背景視圖的方法

- (void)updateBlurBounds

2. SVPIndefiniteAnimatedView 類

這個類提供了一個無線旋轉的動畫,實現方法是把一個顏色漸變的圖片旋轉,然後利用UIBezierPath/CAShapeLayer/Mask等遮住不需要的部分,最後利用CABasicAnimation設置無限旋轉動畫。其中,核心部分是利用layermask屬性實現遮罩功能,而mask的實現方法是顯示顯示bounds的非透明部分,實例圖如下:

mask效果

3. SVProgressAnimatedView 類

這個類提供一個畫圓環的視圖,通過不斷改變layerstrokeEnd的值,實現了進度的顯示。順便提一下,storkeStart使用的預設值是0, 所以是從正上方開始的。

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

4. SVRadialGradientLayer 類

這個類繼承自CALayer,通過CGContextDrawRadialGradient來畫漸變顏色層;其中,CoreFoundation中通過create創建的需要用release釋放,否則會造成記憶體泄漏。

至此,SVProgressHUD分析暫告一段落,分析的不全面的地方,歡迎交流。


參考資料

https://github.com/SVProgressHUD/SVProgressHUD

http://www.jianshu.com/p/a08d4597cf24


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • HP-Socket 是一套通用的高性能 TCP/UDP/HTTP 通信框架,包含服務端組件、客戶端組件和 Agent 組件,廣泛適用於各種不同應用場景的 TCP/UDP/HTTP 通信系統,提供 C/C++、C#、Delphi、E(易語言)、Java、Python 等編程語言介面。HP-Socket... ...
  • 代碼: #import "ViewController.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any a ...
  • 目前將項目中的leancloud的即時通訊改為環信的即時通訊。當引入easeui的時候 出現方法數超過上限的問題。 搜索一下問題,解決方法很簡單。 這裡簡單記錄一下,順序記錄一下此解決方案導致的另一個問題。 一、解決方法數超過64k的問題 問題描述: 解決方案: 1、app目錄下 build.gra ...
  • 思路 思路很簡單,對模型數據操作或則控制界面顯示 先看下json部分數據 這種數據對應的一般都是個tableView, 然後根據章節分開,最終界面如下: 分析 這裡採用UITableViewStylePlain樣式,chapterDtoList對應章,subChapterList對應節。章的話我們使 ...
  • `MBProgressHUD`是一個顯示提示視窗的三方庫,常用於用戶交互、後臺耗時操作等的提示。通過顯示一個提示框,通知用戶操作或任務的執行狀態;同時,利用動畫效果,降低用戶等待的焦慮心理,增強用戶體驗。 本篇文章從源碼角度來看一下 是如何實現的,所用的知識都是比較基礎的,不過還是值得我們學習一下。 ...
  • 近幾年,Retrofit猶如燎原之火搬席卷了整個Android界。要是不懂Retrofit,簡直不好意思出門。。。由於近幾個項目都沒用到Retrofit,無奈只能業餘時間自己擼一下,寫的不好的地方,還請不吝賜教。要集成retrofit,在app的bild.gradle中添加庫以來就可以: 如果需要集 ...
  • 轉載請標明出處:http://blog.csdn.net/zhaoyanjun6/article/details/75126630 本文出自 "【趙彥軍的博客】" 1、前言 在多線程併發編程中Synchronized一直是元老級角色,很多人都會稱呼它為重量級鎖,但是隨著Java SE1.6對Sync ...
  • 引言:最近在做一個iOS端的小游戲,想要實現在安裝時自動關聯好友的功能,就發帖詢問有沒有好的想法。在帖子中法想了這個不錯的SDK,通過它我們還實現了,安裝後自動進入好友游戲房間的功能。這裡我就分享一下,我在集成過程中,發現的一個小技巧。 首先,按照正常的思路,下載導入OpeninstallSDK到工 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...