[TOC]
簡(jiǎn)介
SVProgressHUB是iOS上的的一款loading輕量級(jí)美觀的加載框酒贬。
首先來看下結(jié)構(gòu)目錄
SVIndefiniteAnimatedView 是無限旋轉(zhuǎn)的菊花視圖
SVProgressAnimatedView 是進(jìn)度條視圖
SVRadialGradientLayer 是漸變模式下漸變的背景Layer
使用
顯示
SVProgressHUD提供了顯示加載圖便利方法,而且?guī)缀跞穷惙椒ù浠簦陂_發(fā)中能方便的使用同衣。如
- showWithStatus 顯示加載,并顯示文本
- showProgress 顯示加載壶运,并展示當(dāng)前進(jìn)度條
- showInfoWithStatus 顯示使用Info狀態(tài)的圖片的加載框來替代菊花或者進(jìn)度條
- showSuccessWithStatus 顯示success狀態(tài)的圖片(一個(gè)勾)的加載框來替代菊花或者進(jìn)度條
- showErrorWithStatus 顯示error狀態(tài)的圖片(一個(gè)x)的加載框來替代菊花或者進(jìn)度條
除此之外你也可以設(shè)置自己的圖片耐齐,使用+ (void)showImage:(UIImage*)image status:(NSString*)status;
消失
SVProgressHUD提供了幾種方式來顯示一個(gè)Loding框
- dismiss 直接關(guān)閉一個(gè)加載Hub
- dismissWithDelay 延遲關(guān)閉一個(gè)Hub
- dismissWithCompletion 關(guān)閉Hub帶一個(gè)完成的Block
- dismissWithDelay:completion 延遲關(guān)閉Hub并帶一個(gè)完成的Block
可以看出SVProgressHUD提供了簡(jiǎn)單明了的API,使用便利的類方法讓我們快速在代碼中使用
深入源碼
顯示過程
SVProgressHUD 比較簡(jiǎn)單易懂
在眾多的類方法下面,也實(shí)現(xiàn)相應(yīng)的實(shí)例方法
//簡(jiǎn)單版本
- (void)setStatus:(NSString*)status;
- (void)setFadeOutTimer:(NSTimer*)timer;
- (void)registerNotifications;
- (NSDictionary*)notificationUserInfo;
- (void)positionHUD:(NSNotification*)notification;
- (void)moveToPoint:(CGPoint)newCenter rotateAngle:(CGFloat)angle;
- (void)overlayViewDidReceiveTouchEvent:(id)sender forEvent:(UIEvent*)event;
- (void)showProgress:(float)progress status:(NSString*)status;
- (void)showImage:(UIImage*)image status:(NSString*)status duration:(NSTimeInterval)duration;
- (void)showStatus:(NSString*)status;
- (void)dismiss;
- (void)dismissWithDelay:(NSTimeInterval)delay completion:(SVProgressHUDDismissCompletion)completion;
@end
如何將Hub展示到屏幕上埠况,首先我們從showWithStatus方法往里看耸携。
內(nèi)部實(shí)現(xiàn)
+ (void)showWithStatus:(NSString*)status {
[self sharedView];
[self showProgress:SVProgressHUDUndefinedProgress status:status];
}
在內(nèi)部實(shí)現(xiàn)了一個(gè)單例,這也不難理解為什么可以在內(nèi)方法中快速的顯示一個(gè)Hub
+ (SVProgressHUD*)sharedView {
static dispatch_once_t once;
static SVProgressHUD *sharedView;
#if !defined(SV_APP_EXTENSIONS)
dispatch_once(&once, ^{ sharedView = [[self alloc] initWithFrame:[[[UIApplication sharedApplication] delegate] window].bounds]; });
#else
dispatch_once(&once, ^{ sharedView = [[self alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; });
#endif
return sharedView;
}
之后使用這個(gè)單例視圖來完成Hub的顯示辕翰,那么showProgress這個(gè)方法又做什么呢夺衍?首先我們看下簡(jiǎn)化版代碼
- (void)showProgress:(float)progress status:(NSString*)status {
__weak SVProgressHUD *weakSelf = self;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
__strong SVProgressHUD *strongSelf = weakSelf;
if(strongSelf){
//更新視圖層級(jí),將SV放到當(dāng)前window最前面
[strongSelf updateViewHierachy];
//判斷如果是進(jìn)度條喜命,使用進(jìn)度條View
if(progress >= 0) {
// Add ring to HUD and set progress
[strongSelf.hudView addSubview:strongSelf.ringView];
[strongSelf.hudView addSubview:strongSelf.backgroundRingView];
strongSelf.ringView.strokeEnd = progress;
} else {
// 使用無限旋轉(zhuǎn)的菊花模式
[strongSelf.hudView addSubview:strongSelf.indefiniteAnimatedView];
}
// Show
[strongSelf showStatus:status];
}
}];
}
可以看出沟沙,SV為了將顯示操作放到主線程使用了[NSOperationQueue mainQueue]
來確保在主線程操作UI,
在updateViewHierachy
方法中壁榕,SV會(huì)尋找提供Hub顯示的Window矛紫,并且將自己添加到上面。使用手法為:
NSEnumerator *frontToBackWindows = [UIApplication.sharedApplication.windows reverseObjectEnumerator];
for (UIWindow *window in frontToBackWindows) {
BOOL windowOnMainScreen = window.screen == UIScreen.mainScreen;
BOOL windowIsVisible = !window.hidden && window.alpha > 0;
BOOL windowLevelNormal = window.windowLevel == UIWindowLevelNormal;
if(windowOnMainScreen && windowIsVisible && windowLevelNormal) {
[window addSubview:self.overlayView];
break;
}
}
找出當(dāng)前的windowLevelNormal,并且是顯示的UI牌里,將自己顯示在其之上颊咬,但是也有點(diǎn)缺點(diǎn),就是如果需要在自定義的window上面顯示Hub將無能為力牡辽。
找到window之后喳篇,生成菊花或者進(jìn)度條View,然后設(shè)置label态辛,計(jì)算出大小麸澜,然后完成顯示
動(dòng)畫的繪制
SVIndefiniteAnimatedView是無限旋轉(zhuǎn)的View。其中使用CASharpLayer和LayerMask來完成一個(gè)旋轉(zhuǎn)動(dòng)畫奏黑,使用一張漸變的圖片炊邦,設(shè)置其mask屬性,然后加上旋轉(zhuǎn)動(dòng)畫攀涵,來形成一個(gè)漂亮的加載動(dòng)畫
動(dòng)畫繪制代碼
UIBezierPath* smoothedPath = [UIBezierPath bezierPathWithArcCenter:arcCenter radius:self.radius startAngle:(CGFloat) (M_PI*3/2) endAngle:(CGFloat) (M_PI/2+M_PI*5) clockwise:YES];
_indefiniteAnimatedLayer = [CAShapeLayer layer];
_indefiniteAnimatedLayer.contentsScale = [[UIScreen mainScreen] scale];
_indefiniteAnimatedLayer.frame = CGRectMake(0.0f, 0.0f, arcCenter.x*2, arcCenter.y*2);
_indefiniteAnimatedLayer.fillColor = [UIColor clearColor].CGColor;
_indefiniteAnimatedLayer.strokeColor = self.strokeColor.CGColor;
_indefiniteAnimatedLayer.lineWidth = self.strokeThickness;
_indefiniteAnimatedLayer.lineCap = kCALineCapRound;
_indefiniteAnimatedLayer.lineJoin = kCALineJoinBevel;
_indefiniteAnimatedLayer.path = smoothedPath.CGPath;
CALayer *maskLayer = [CALayer layer];
NSBundle *bundle = [NSBundle bundleForClass:[SVProgressHUD class]];
NSURL *url = [bundle URLForResource:@"SVProgressHUD" withExtension:@"bundle"];
NSBundle *imageBundle = [NSBundle bundleWithURL:url];
NSString *path = [imageBundle pathForResource:@"angle-mask" ofType:@"png"];
maskLayer.contents = (__bridge id)[[UIImage imageWithContentsOfFile:path] CGImage];
maskLayer.frame = _indefiniteAnimatedLayer.bounds;
_indefiniteAnimatedLayer.mask = maskLayer;
NSTimeInterval animationDuration = 1;
CAMediaTimingFunction *linearCurve = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
animation.fromValue = (id) 0;
animation.toValue = @(M_PI*2);
animation.duration = animationDuration;
animation.timingFunction = linearCurve;
animation.removedOnCompletion = NO;
animation.repeatCount = INFINITY;
animation.fillMode = kCAFillModeForwards;
animation.autoreverses = NO;
[_indefiniteAnimatedLayer.mask addAnimation:animation forKey:@"rotate"];
CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
animationGroup.duration = animationDuration;
animationGroup.repeatCount = INFINITY;
animationGroup.removedOnCompletion = NO;
animationGroup.timingFunction = linearCurve;
CABasicAnimation *strokeStartAnimation = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
strokeStartAnimation.fromValue = @0.015;
strokeStartAnimation.toValue = @0.515;
CABasicAnimation *strokeEndAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
strokeEndAnimation.fromValue = @0.485;
strokeEndAnimation.toValue = @0.985;
animationGroup.animations = @[strokeStartAnimation, strokeEndAnimation];
[_indefiniteAnimatedLayer addAnimation:animationGroup forKey:@"progress"];
圓環(huán)動(dòng)畫比較簡(jiǎn)單铣耘,原理是使用CAShareLayer,并且設(shè)置的endPath來完成一個(gè)由progress驅(qū)動(dòng)的進(jìn)度條動(dòng)畫
CGPoint arcCenter = CGPointMake(self.radius+self.strokeThickness/2+5, self.radius+self.strokeThickness/2+5);
UIBezierPath* smoothedPath = [UIBezierPath bezierPathWithArcCenter:arcCenter radius:self.radius startAngle:(CGFloat)-M_PI_2 endAngle:(CGFloat) (M_PI + M_PI_2) clockwise:YES];
_ringAnimatedLayer = [CAShapeLayer layer];
_ringAnimatedLayer.contentsScale = [[UIScreen mainScreen] scale];
_ringAnimatedLayer.frame = CGRectMake(0.0f, 0.0f, arcCenter.x*2, arcCenter.y*2);
_ringAnimatedLayer.fillColor = [UIColor clearColor].CGColor;
_ringAnimatedLayer.strokeColor = self.strokeColor.CGColor;
_ringAnimatedLayer.lineWidth = self.strokeThickness;
_ringAnimatedLayer.lineCap = kCALineCapRound;
_ringAnimatedLayer.lineJoin = kCALineJoinBevel;
_ringAnimatedLayer.path = smoothedPath.CGPath;
消失過程
消失調(diào)用的是dismissWithDelay:completion
方法
消失過程和顯示過程相反以故,大致是
- 在mainQueue中添加一個(gè)Block的operation(在主線程更改UI)
- 將自己從SuperView中刪除蜗细,
- 如果指定了延遲,那么設(shè)置一個(gè)timer
- 在消失中如果指定動(dòng)畫怒详,那么加一個(gè)fade的動(dòng)畫來進(jìn)行消失
小結(jié)
- SV將View添加到Window上
- SV使用NSOperationQueue 來確保自己在主線程修改UI
- SV的動(dòng)畫使用layer來繪制
優(yōu)點(diǎn):API簡(jiǎn)單明了炉媒,調(diào)用方便
缺點(diǎn):默認(rèn)在APP的UIWindow,不好定制