提示圖顯示篇之MBProgressHUD(三)

版本記錄

版本號 時間
V1.0 2017.05.28

前言

在我們的app項目中,為了增加和用戶很好的交互能力,通常都需要加一些提示圖,比如說,當我們需要網(wǎng)絡加載數(shù)據(jù)的時候,首先要監(jiān)測網(wǎng)絡,如果網(wǎng)絡斷開的時候,我們需要提示用戶;還有一個場景就是登陸的時候,需要提示用戶正在登錄中和登錄成功;再比如清除用戶的緩存數(shù)據(jù)成功的時候,也需要進行清除成功的提示的,等等。總之,用的場景很多,好的提示圖可以增強和用戶的交互體驗,試想,如果沒有網(wǎng)絡,也不提示用戶,用戶還以為還在登錄,過了一會還是上不去,那可能用戶就瘋掉了,怒刪app了。最近做的幾個項目中也是對這個要求的也很多,在實際應用中可以自己寫,也可以使用第三方框架,比較知名的比如MBProgressHUDSVProgressHUD,從這一篇開始我就一點一點的介紹它們以及它們的使用方法,希望對大家有所幫助,那我們就開始嘍。先給出github地址:
MBProgressHUD github
感興趣可以先看上一篇
1.提示圖顯示篇之MBProgressHUD(一)
2.提示圖顯示篇之MBProgressHUD(二)

這一篇將對MBProgreeHUD的初始化和動畫效果等進行介紹。

詳情

一、初始化方法

任何對象都需要進行初始化,MBProgressHUD也不例外,初始化才能在內(nèi)存中分配空間,才會存取值和運行,代碼才會有硬件依托。下面我們先看一下MBProgressHUD的初始化方法。

1. 第一種初始化方法
- (id)initWithWindow:(UIWindow *)window __attribute__((deprecated("Use initWithView: instead.")));

這種初始化方法已經(jīng)被廢棄了,Window修改為View。

2. 這個才是常用的初始化方法
/**
 * A convenience constructor that initializes the HUD with the view's bounds. Calls the designated constructor with
 * view.bounds as the parameter.
 *
 * @param view The view instance that will provide the bounds for the HUD. Should be the same instance as
 * the HUD's superview (i.e., the view that the HUD will be added to).
 */

- (instancetype)initWithView:(UIView *)view;

下面我們就以一個小的demo來說明下MBProgressHUD的初始化方法。

    UIImageView *imageView = [[UIImageView alloc] init];
    imageView.image = [UIImage imageNamed:@"global"];

    MBProgressHUD *HUD = [[MBProgressHUD alloc] initWithView:self.view];
    HUD.mode = MBProgressHUDModeCustomView;
    // 顯示隱藏時的動畫模式
    HUD.animationType = MBProgressHUDAnimationFade;
    // 關閉繪制的"性能開關",如果alpha不為1,最好將opaque設為NO,讓繪圖系統(tǒng)優(yōu)化性能
    HUD.opaque = NO;
    HUD.customView = imageView;
    HUD.label.text = @"全球化";
    HUD.detailsLabel.text = @"我們一起參與";
    [self.view addSubview:HUD];
    [HUD showAnimated:YES];
    [HUD hideAnimated:YES afterDelay:10.0];
初始化圖案

上面的就是利用MBProgressHUD *HUD = [[MBProgressHUD alloc] initWithView:self.view]方法來實現(xiàn)的。

如果有特殊的需求需要封裝的話,可以自定義一個類繼承自MBProgressHUD,然后采用UIView的初始化方法進行初始化和配置,也就是用下面的方法。

- (instanceType)initWithFrame:(CGRect)frame;

二、動畫效果

看MBProgressHUD.h文件前幾個就是定義幾個枚舉,有個我們前面已經(jīng)說了,那就是指示圖的樣式,下面我們看另外一個枚舉,那就是動畫效果的枚舉定義,如下所示:

typedef NS_ENUM(NSInteger, MBProgressHUDAnimation) {
    // 默認效果,只有透明度變化的動畫效果
    MBProgressHUDAnimationFade,
    // 透明度變化+形變效果,其中MBProgressHUDAnimationZoom和
    // MBProgressHUDAnimationZoomOut的枚舉值都為1
    MBProgressHUDAnimationZoom,
    //拉遠鏡頭, 先把形變放大到1.5倍,再恢復原狀,產(chǎn)生縮小效果
    MBProgressHUDAnimationZoomOut = MBProgressHUDAnimationZoom,
    //拉近鏡頭, 先把形變縮小到0.5倍,再恢復到原狀,產(chǎn)生放大效果
    MBProgressHUDAnimationZoomIn
};

這里默認效果就是MBProgressHUDAnimationFade。

動畫顯示效果主要是在下面兩個方法中實現(xiàn)的。

  • 1.HUD的顯示
//HUD的顯示
- (void)showUsingAnimation:(BOOL)animated 
{
    // Cancel any scheduled hideDelayed: calls
    [NSObject cancelPreviousPerformRequestsWithTarget:self];
    [self setNeedsDisplay];

    // ZoomIn,ZoomOut分別理解為`拉近鏡頭`,`拉遠鏡頭`
    // 因此MBProgressHUDAnimationZoomIn先把形變縮小到0.5倍,再恢復到原狀,產(chǎn)生放大效果
    // 反之MBProgressHUDAnimationZoomOut先把形變放大到1.5倍,再恢復原狀,產(chǎn)生縮小效果
    // 要注意的是,形變的是整個`MBProgressHUD`,而不是中間可視部分
    if (animated && animationType == MBProgressHUDAnimationZoomIn) {
    // 在初始化方法中, 已經(jīng)定義了rotationTransform = CGAffineTransformIdentity.
    // CGAffineTransformIdentity也就是對view不進行變形,對view進行仿射變化總是原樣

    // CGAffineTransformConcat是兩個矩陣相乘,與之等價的設置方式是:
    // self.transform = CGAffineTransformScale(rotationTransform, 0.5f, 0.5f);
        self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(0.5f, 0.5f));
    } else if (animated && animationType == MBProgressHUDAnimationZoomOut) {
    // self.transform = CGAffineTransformScale(rotationTransform, 1.5f, 1.5f);
        self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(1.5f, 1.5f));
    }

    self.showStarted = [NSDate date];

    // 開始做動畫
    if (animated) {
    // 在初始化方法或者`hideUsingAnimation:`方法中,alpha被設置為0.f,在該方法中完成0.f~1.f的動畫效果
        [UIView beginAnimations:nil context:NULL];
        [UIView setAnimationDuration:0.30];
        self.alpha = 1.0f;
    // 從形變狀態(tài)回到初始狀態(tài)    
        if (animationType == MBProgressHUDAnimationZoomIn || animationType == MBProgressHUDAnimationZoomOut) {
            self.transform = rotationTransform;
        }
        [UIView commitAnimations];
    }
    else {
        self.alpha = 1.0f;
    }
}
  • 2.HUD的隱藏
// HUD的隱藏
- (void)hideUsingAnimation:(BOOL)animated 
{
    // Fade out
    if (animated && showStarted) {
        [UIView beginAnimations:nil context:NULL];
        [UIView setAnimationDuration:0.30];
        [UIView setAnimationDelegate:self];
        [UIView setAnimationDidStopSelector:@selector(animationFinished:finished:context:)];
          // 當alpha小于0.01時,就會被當做全透明對待,全透明是接收不了觸摸事件的.
          // 所以設置0.02防止hud在還沒結束動畫并調用done方法之前傳遞觸摸事件.
          // 在完成的回調animationFinished:finished:context:才設為0
        if (animationType == MBProgressHUDAnimationZoomIn) {
            self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(1.5f, 1.5f));
        } else if (animationType == MBProgressHUDAnimationZoomOut) {
            self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(0.5f, 0.5f));
        }

        self.alpha = 0.02f;
        [UIView commitAnimations];
    }
    else {
        self.alpha = 0.0f;
        [self done];
    }
    self.showStarted = nil;
}
  • 3.HUD指示器的實現(xiàn)
- (void)updateIndicators 
{

    // 讀源碼的時候,類似這種局部變量直接忽略,等代碼用到它,我們再"懶加載"
    BOOL isActivityIndicator = [indicator isKindOfClass:[UIActivityIndicatorView class]];
    BOOL isRoundIndicator = [indicator isKindOfClass:[MBRoundProgressView class]];

    // 如果模式是MBProgressHUDModeIndeterminate,將使用系統(tǒng)自帶的菊花系列指示器
    if (mode == MBProgressHUDModeIndeterminate) {
            // 再看回最上面的兩條語句                
              // 初始化的時候進來,indicator是空的,對空對象發(fā)送消息返回的布爾值是NO
            // 因為在初始化完畢后,用戶可能會設置mode屬性,那時還會進入這個方法,所以這兩個布爾變量除了第一次以外是有用的
        if (!isActivityIndicator) {
            // 默認第一次會進入到這里,對nil發(fā)送消息不會發(fā)生什么事
            // 為什么要removeFromSuperview呢,因為這方法并不會只進入一次
            // 不排除有些情況下先改變了mode到其他模式,之后又改回來了,這時候如果不移除
            // MBProgressHUD就會殘留子控件在subviews里,雖然界面并不會顯示它
            [indicator removeFromSuperview];
            // 使用系統(tǒng)自帶的巨大白色菊花
            // 系統(tǒng)菊花有三種
            //typedef NS_ENUM(NSInteger, UIActivityIndicatorViewStyle) {
                    //    UIActivityIndicatorViewStyleWhiteLarge, // 大又白
                    //    UIActivityIndicatorViewStyleWhite, // 小白
                    //    UIActivityIndicatorViewStyleGray,  // 小灰
                //};
            self.indicator = MB_AUTORELEASE([[UIActivityIndicatorView alloc]
                                             initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]);
            [(UIActivityIndicatorView *)indicator startAnimating];
            [self addSubview:indicator];
        }
        // 系統(tǒng)菊花能設置顏色是從iOS5開始(NS_AVAILABLE_IOS(5_0)),這里用宏對手機版本進行了判斷
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 50000
        [(UIActivityIndicatorView *)indicator setColor:self.activityIndicatorColor];
#endif
    }
            // 源碼實現(xiàn)了兩種自定義視圖
            // 一種是MBBarProgressView(進度條),另一種是MBRoundProgressView(圓餅or圓環(huán))

    else if (mode == MBProgressHUDModeDeterminateHorizontalBar) {
            // 進度條樣式
        [indicator removeFromSuperview];
        self.indicator = MB_AUTORELEASE([[MBBarProgressView alloc] init]);
        [self addSubview:indicator];
    }

    else if (mode == MBProgressHUDModeDeterminate || mode == MBProgressHUDModeAnnularDeterminate) {
            // 這兩種mode都產(chǎn)生MBRoundProgressView視圖,MBRoundProgressView又分兩種樣式
            // 如果你設置了mode為MBProgressHUDModeDeterminate,那么流程是這樣子的
            // 1)alloc init先生成系統(tǒng)的MBProgressHUDModeIndeterminate模式->
            // 2)設置了mode為餅圖,觸發(fā)KVO,又進入了updateIndicators方法->
            // 3)由于isRoundIndicator是No,產(chǎn)生餅狀圖

            // 如果設置了MBProgressHUDModeAnnularDeterminate,那么步驟比它多了一步,
            // 1)alloc init先生成系統(tǒng)的MBProgressHUDModeIndeterminate模式->
            // 2)設置了mode為圓環(huán),觸發(fā)KVO,又進入了updateIndicators方法->
            // 3)由于isRoundIndicator是No,產(chǎn)生餅狀圖->
            // 4)設置[(MBRoundProgressView *)indicator setAnnular:YES]觸發(fā)MBRoundProgressView的
            // KVO進行重繪視圖產(chǎn)生圓環(huán)圖
        if (!isRoundIndicator) {
            // 個人認為這個isRoundIndicator變量純屬多余
            // isRoundIndicator為Yes的情況只有從MBProgressHUDModeDeterminate換成MBProgressHUDModeAnnularDeterminate
            // 或者MBProgressHUDModeAnnularDeterminate換成MBProgressHUDModeDeterminate
            // 而實際上這兩種切換方式產(chǎn)生的視圖都是圓環(huán),這是由于沒有讓annular設置成No
            [indicator removeFromSuperview];
            self.indicator = MB_AUTORELEASE([[MBRoundProgressView alloc] init]);
            [self addSubview:indicator];
        }
        if (mode == MBProgressHUDModeAnnularDeterminate) {
            [(MBRoundProgressView *)indicator setAnnular:YES];
        }
    }

    else if (mode == MBProgressHUDModeCustomView && customView != indicator) {
        // 自定義視圖
        [indicator removeFromSuperview];
        self.indicator = customView;
        [self addSubview:indicator];
    }

   else if (mode == MBProgressHUDModeText) {
          // 只有文字的模式
        [indicator removeFromSuperview];
        self.indicator = nil;
    }
}

  • 4.HUD布局

??下面我們看一下MBProgressHUD的子控件布局,它的子控件包括指示器、label、detailLabel,還要注意的是MBProgressHUD的背景是整個屏幕的,當我們設置MBProgressHUD的backgroundColor,會發(fā)現(xiàn)確實是整個屏幕的顏色都改變了。下面我們看一下子控件的布局。

- (void)layoutSubviews 
{
    [super layoutSubviews];

    // MBProgressHUD是一個充滿整個父控件的控件
    // 使得父控件的交互完全被屏蔽
    UIView *parent = self.superview;
    if (parent) {
        self.frame = parent.bounds;
    }
    CGRect bounds = self.bounds;

    .......

    // 如果用戶設置了square屬性,就會盡量讓它顯示成正方形
    if (square) {
    // totalSize為下圖藍色框框的size
        CGFloat max = MAX(totalSize.width, totalSize.height);
        if (max <= bounds.size.width - 2 * margin) {
            totalSize.width = max;
        }
        if (max <= bounds.size.height - 2 * margin) {
            totalSize.height = max;
        }
    }
    if (totalSize.width < minSize.width) {
        totalSize.width = minSize.width;
    }
    if (totalSize.height < minSize.height) {
        totalSize.height = minSize.height;
    }

    size = totalSize;
}

具體布局可以參考下面的布局,這個圖是別人畫的,具體參考文章和博客我下面會列出,謝謝他的分享。

布局示意圖

??上圖藍色虛線部分代表子控件們能夠展示的區(qū)域,其中寬度是被限制的,其中定義了maxWidth讓3個子控件中的最大寬度都不得超過它。值得注意的是,源碼并沒設置最大高度,如果我們使用自定義的視圖,高度夠大就會使藍色虛線部分的上下底超出屏幕范圍,某種程度上來講也是設計上的一種bug,但是畢竟還是不影響正常的使用,畢竟沒有人會用很大的自定義視圖。
??此外,綠色的label被限制為只能顯示一行,黃色的detailLabel通過下面的代碼來限制它不能超出屏幕上下。

// 計算出屏幕剩下的高度
// 其中減去了4個margin大小,保證了子空間和HUD的邊距,HUD和屏幕的距離
CGFloat remainingHeight = bounds.size.height - totalSize.height - kPadding - 4 * margin;

// 將文字內(nèi)容限制在這個size中,超出部分省略號
CGSize maxSize = CGSizeMake(maxWidth, remainingHeight);

CCGSize detailsLabelSize = MB_MULTILINE_TEXTSIZE(detailsLabel.text, detailsLabel.font, maxSize, detailsLabel.lineBreakMode);

// 7.0開始使用boundingRectWithSize:options:attributes:context:方法計算
// 7.0以前使用sizeWithFont:constrainedToSize:lineBreakMode:計算
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000
#define MB_MULTILINE_TEXTSIZE(text, font, maxSize, mode) [text length] > 0 ? [text \
boundingRectWithSize:maxSize options:(NSStringDrawingUsesLineFragmentOrigin) \
attributes:@{NSFontAttributeName:font} context:nil].size : CGSizeZero;
#else 
#define MB_MULTILINE_TEXTSIZE(text, font, maxSize, mode) [text length] > 0 ? [text \
sizeWithFont:font constrainedToSize:maxSize lineBreakMode:mode] : CGSizeZero;
#endif

下圖是另一種沒達到maxSize的情況。

沒達到maxSize的情況

參考文章和博客

1. 源碼筆記---MBProgressHUD

后記

??這一篇主要寫的是MBProgressHUD的初始化方法以及動畫方法,未完,待續(xù)~~~

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

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

  • 版本記錄 前言 在我們的app項目中,為了增加和用戶很好的交互能力,通常都需要加一些提示圖,比如說,當我們需要網(wǎng)絡...
    刀客傳奇閱讀 1,320評論 0 1
  • 簡介: MBProgressHUD是一個iOS插件類,當在后臺線程進行工作時,它會顯示一個帶有指示器和/或標簽的半...
    Leon_520閱讀 2,003評論 0 5
  • 源碼來源:gitHub源碼 轉載于: CocoaChina 來源:南峰子的技術博客 版本:0.9.1 MBPr...
    李小六_閱讀 6,449評論 2 5
  • 【萵苣苔】農(nóng)家的飯桌上,最使我懷念的是門口菜園里的萵苣苔。蘇北鄉(xiāng)里人家屋舍布局大致如此:正南為堂屋,朝東為鍋屋,再...
    大城小虎閱讀 1,215評論 0 0
  • 本篇核心以善聽、善知;《捭闔》側重說,而《反應》側重聽。 古之大化者,乃與無形俱生。反以觀往,復以驗來;反以知古,...
    墨非_II閱讀 900評論 0 1