提示圖顯示篇之MBProgressHUD(四)

版本記錄

版本號 時(shí)間
V1.0 2017.05.28

前言

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

這一篇將對MBProgreeHUD的繪制和顯示隱藏進(jìn)行介紹。

詳情

一、繪制

    1. 背景視圖的繪制

當(dāng)我們設(shè)置好MBProgressHUD的屬性等因素后,然后我們需要做的就是將它們渲染到屏幕上,就是我們看到的提示圖了。

- (void)drawRect:(CGRect)rect 
{
    // 拿到當(dāng)前的繪圖上下文
    CGContextRef context = UIGraphicsGetCurrentContext();
    UIGraphicsPushContext(context);

    // 默認(rèn)中間的HUD外是透明的,可以看到父控件,設(shè)置了dimBackground這個(gè)屬性可以讓HUD周圍是一個(gè)漸變色的背景.
    // 這里用了一個(gè)漸變層,顏色是寫死的
    if (self.dimBackground) {
        //Gradient colours
        size_t gradLocationsNum = 2;
        CGFloat gradLocations[2] = {0.0f, 1.0f};
        CGFloat gradColors[8] = {0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.75f};
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, gradColors, gradLocations, gradLocationsNum);
        CGColorSpaceRelease(colorSpace);
        //Gradient center
        CGPoint gradCenter= CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2);
        //Gradient radius
        float gradRadius = MIN(self.bounds.size.width , self.bounds.size.height) ;
        //Gradient draw
        CGContextDrawRadialGradient (context, gradient, gradCenter,
                                     0, gradCenter, gradRadius,
                                     kCGGradientDrawsAfterEndLocation);
        CGGradientRelease(gradient);
    }

    // 用戶有設(shè)置顏色就使用設(shè)置的顏色,沒有的話默認(rèn)灰色
    // 從下面代碼可以看出,自定義HUD背景顏色是沒有透明度的
    if (self.color) {
        CGContextSetFillColorWithColor(context, self.color.CGColor);
    } else {
        CGContextSetGrayFillColor(context, 0.0f, self.opacity);
    }

    CGRect allRect = self.bounds;
    // 畫出一個(gè)圓角的HUD
    // size在layoutSubviews中被計(jì)算出來,是HUD的真實(shí)size
    CGRect boxRect = CGRectMake(round((allRect.size.width - size.width) / 2) + self.xOffset,
                                round((allRect.size.height - size.height) / 2) + self.yOffset, size.width, size.height);
    float radius = self.cornerRadius;
    //開始繪制路徑
    CGContextBeginPath(context);
    // 起始點(diǎn)
    CGContextMoveToPoint(context, CGRectGetMinX(boxRect) + radius, CGRectGetMinY(boxRect));
    // 依次畫出右上角、右下角,左下角,左上角的四分之一圓弧
    // 注意,雖然沒有顯式地調(diào)用CGContextAddLineToPoint函數(shù)
    // 但繪制圓弧時(shí)每一次的起點(diǎn)都會和上一次的終點(diǎn)連接,生成線段
    CGContextAddArc(context, CGRectGetMaxX(boxRect) - radius, CGRectGetMinY(boxRect) + radius, radius, 3 * (float)M_PI / 2, 0, 0);
    CGContextAddArc(context, CGRectGetMaxX(boxRect) - radius, CGRectGetMaxY(boxRect) - radius, radius, 0, (float)M_PI / 2, 0);
    CGContextAddArc(context, CGRectGetMinX(boxRect) + radius, CGRectGetMaxY(boxRect) - radius, radius, (float)M_PI / 2, (float)M_PI, 0);
    CGContextAddArc(context, CGRectGetMinX(boxRect) + radius, CGRectGetMinY(boxRect) + radius, radius, (float)M_PI, 3 * (float)M_PI / 2, 0);
    CGContextClosePath(context);
    CGContextFillPath(context);

    UIGraphicsPopContext();
}
  • 2.indicator的繪制

MBRoundProgressView的繪制

下面說一下圓形指示器的繪制。

當(dāng)我們繪制路徑時(shí),描述的路徑如果寬度大于1,描邊的時(shí)候是向路徑寬度是以路徑為中點(diǎn)的。舉個(gè)例子,如果從(0,0)向(100,0)畫一條寬度為X的線,那么顯示的寬度實(shí)際只有X/2,因?yàn)檫€有一半因?yàn)槌隽死L圖區(qū)域而沒有被繪制。為了防止繪制內(nèi)容的丟失,半徑radius的計(jì)算是(self.bounds.size.width - lineWidth)/2,而并不是self.bounds.size.width/2,更不是(self.bounds.size.width -2*lineWidth)/2。

看下面的代碼

    // 圓環(huán)繪制    
    if (_annular) {
        // iOS7.0以后的圓環(huán)描邊風(fēng)格變了,變成了2.f
          // 7.0之前的還是5.f.主要是為了迎合扁平的風(fēng)格我覺得
        BOOL isPreiOS7 = kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_7_0;
        CGFloat lineWidth = isPreiOS7 ? 5.f : 2.f;
        ......
        CGFloat radius = (self.bounds.size.width - lineWidth)/2;
    }

MBBarProgressView的繪制

MBBarProgressView與MBRoundProgressView的繪制類似,都是使用Quartz2D進(jìn)行繪圖。

看下面的代碼

   .....
    // Draw background
    float radius = (rect.size.height / 2) - 2;
    CGContextMoveToPoint(context, 2, rect.size.height/2);
    CGContextAddArcToPoint(context, 2, 2, radius + 2, 2, radius);
    //CGContextAddLineToPoint(context, rect.size.width - radius - 2, 2);
    CGContextAddArcToPoint(context, rect.size.width - 2, 2, rect.size.width - 2, rect.size.height / 2, radius);
    CGContextAddArcToPoint(context, rect.size.width - 2, rect.size.height - 2, rect.size.width - radius - 2, rect.size.height - 2, radius);
    //CGContextAddLineToPoint(context, radius + 2, rect.size.height - 2);
    CGContextAddArcToPoint(context, 2, rect.size.height - 2, 2, rect.size.height/2, radius);
    CGContextFillPath(context);

二、視圖的顯示和隱藏

  • 顯示

顯示的實(shí)現(xiàn)代碼可以參考下面:

- (void)showWhileExecuting:(SEL)method onTarget:(id)target withObject:(id)object animated:(BOOL)animated 
{
    methodForExecution = method;
      // 對于MRC來說,要保留target和object對象
      // ARC會自動保留這兩個(gè)對象
      // 不管是ARC還是MRC,都要注意引用循環(huán)的問題,因此下面有個(gè)-cleanUp方法用來釋放強(qiáng)引用
    targetForExecution = MB_RETAIN(target);
    objectForExecution = MB_RETAIN(object);

    self.taskInProgress = YES;
      // detachNewThreadSelector是NSThread的類方法,開啟一個(gè)子線程執(zhí)行任務(wù),線程默認(rèn)start
    [NSThread detachNewThreadSelector:@selector(launchExecution) toTarget:self withObject:nil];
    // Show HUD view
    [self show:animated];
}

- (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue
     completionBlock:(MBProgressHUDCompletionBlock)completion 
{
    // 標(biāo)記任務(wù)標(biāo)識
    self.taskInProgress = YES;
    // 將block先引用起來,在隱藏完之后執(zhí)行block
    self.completionBlock = completion;
    // 在隊(duì)列上異步執(zhí)行,更新UI在主線程進(jìn)行
    dispatch_async(queue, ^(void) {
        block();
        dispatch_async(dispatch_get_main_queue(), ^(void) {
              // 方法中有隱藏HUD這一更新UI的操作
            [self cleanUp];
        });
    });
    // 在任務(wù)執(zhí)行的過程中進(jìn)行動畫
    [self show:animated];
}

- (void)launchExecution 
{
    // 對于多線程操作建議把線程操作放到@autoreleasepool中
    @autoreleasepool {
      // 忽略警告的編譯器指令
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        // 究其原因,編譯期時(shí)編譯器并不知道m(xù)ethodForExecution是什么
          // ARC的內(nèi)存管理是建立在規(guī)范的命名規(guī)則之上的,不知道方法名是什么就不知道如何處理返回值
          // 如果該方法有返回值,就不知道返回值是加入了自動釋放池的還是需要ARC釋放的對象
          // 因此ARC不對返回值執(zhí)行任何操作,如果返回值并不是加入自動釋放池的對象,這時(shí)就內(nèi)存泄露了
        [targetForExecution performSelector:methodForExecution withObject:objectForExecution];
#pragma clang diagnostic pop

        [self performSelectorOnMainThread:@selector(cleanUp) withObject:nil waitUntilDone:NO];
    }
}

- (void)cleanUp 
{
    // 任務(wù)標(biāo)識重置    
    taskInProgress = NO;
#if !__has_feature(objc_arc)
    [targetForExecution release];
    [objectForExecution release];
#else
    targetForExecution = nil;
    objectForExecution = nil;
#endif
    [self hide:useAnimation];
}

這里,taskInProgress的意思要結(jié)合graceTime來看,graceTime是為了防止hud只顯示很短時(shí)間(一閃而過)的情況,給用戶設(shè)定的一個(gè)屬性,如果任務(wù)在graceTime內(nèi)完成,將不會showhud。所以graceTime這個(gè)屬性離開了賦給hud的任務(wù)就沒意義了,因此,taskInProgress用來標(biāo)識是否帶有執(zhí)行的任務(wù)。

- (void)handleGraceTimer:(NSTimer *)theTimer 
{
    // 如果沒有任務(wù),設(shè)置了graceTime也沒有意義
    if (taskInProgress) {
        [self showUsingAnimation:useAnimation];
    }
}

值得注意的是,通過showWhileExecuting:onTarget:withObject:animated:等方法時(shí),會自動將taskInProgress置為yes,其他情況(任務(wù)所在的線程不是由hud內(nèi)部所創(chuàng)建的)需手動設(shè)置這個(gè)屬性。

- (void)show:(BOOL)animated 
{
    ......
      // 進(jìn)行self.graceTime的延時(shí)之后,才調(diào)用handleGraceTimer:顯示hud
      // 如果沒到時(shí)間就執(zhí)行完了,那么完成任務(wù)調(diào)用的done方法會把taskInProgress設(shè)為NO,那么就不會顯示hud了
    if (self.graceTime > 0.0) {
        NSTimer *newGraceTimer = [NSTimer timerWithTimeInterval:self.graceTime target:self selector:@selector(handleGraceTimer:) userInfo:nil repeats:NO];
        [[NSRunLoop currentRunLoop] addTimer:newGraceTimer forMode:NSRunLoopCommonModes];
        self.graceTimer = newGraceTimer;
    }
   ......
}
  • 隱藏

下面是隱藏實(shí)現(xiàn)的代碼邏輯。


- (void)hide:(BOOL)animated afterDelay:(NSTimeInterval)delay 
{
    [self performSelector:@selector(hideDelayed:) withObject:[NSNumber numberWithBool:animated] afterDelay:delay];
}

- (void)hideDelayed:(NSNumber *)animated 
{
    [self hide:[animated boolValue]];
}

- (void)hide:(BOOL)animated 
{
    NSAssert([NSThread isMainThread], @"MBProgressHUD needs to be accessed on the main thread.");
    useAnimation = animated;
    // 設(shè)置一個(gè)最短的顯示時(shí)間
      // showStarted在顯示的時(shí)候被設(shè)置了,用當(dāng)前的時(shí)間算出距離showStarted過了多少時(shí)間
      // 得出interv.如果沒有達(dá)到minShowTimer所要求的時(shí)間,就開啟定時(shí)器等待到指定的最短時(shí)間
    if (self.minShowTime > 0.0 && showStarted) {
        NSTimeInterval interv = [[NSDate date] timeIntervalSinceDate:showStarted];
        if (interv < self.minShowTime) {
            self.minShowTimer = [NSTimer scheduledTimerWithTimeInterval:(self.minShowTime - interv) target:self
                                                               selector:@selector(handleMinShowTimer:) userInfo:nil repeats:NO];
            return;
        }
    }
    // ... otherwise hide the HUD immediately
    [self hideUsingAnimation:useAnimation];
}

參考文章和博客

1. 源碼筆記---MBProgressHUD

后記

這里主要寫的的繪制原理以及顯示隱藏的代碼實(shí)現(xiàn),后面還會深入的舉例說明MBProgressHUD的使用方法,希望對大家有所幫助,謝謝大家~~~

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

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