版本記錄
版本號 | 時(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)用中可以自己寫,也可以使用第三方框架,比較知名的比如MBProgressHUD和SVProgressHUD,從這一篇開始我就一點(diǎn)一點(diǎn)的介紹它們以及它們的使用方法,希望對大家有所幫助,那我們就開始嘍。先給出github地址:
MBProgressHUD github
感興趣可以先看上一篇
1.提示圖顯示篇之MBProgressHUD(一)
2.提示圖顯示篇之MBProgressHUD(二)
3.提示圖顯示篇之MBProgressHUD(三)
這一篇將對MBProgreeHUD的繪制和顯示隱藏進(jìn)行介紹。
詳情
一、繪制
- 背景視圖的繪制
當(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];
}
參考文章和博客
后記
這里主要寫的的繪制原理以及顯示隱藏的代碼實(shí)現(xiàn),后面還會深入的舉例說明MBProgressHUD的使用方法,希望對大家有所幫助,謝謝大家~~~