SVProgressHUD

[TOC]

簡介

SVProgressHUB是iOS上的的一款loading輕量級美觀的加載框。
首先來看下結(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)的圖片(一個勾)的加載框來替代菊花或者進(jìn)度條
  • showErrorWithStatus 顯示error狀態(tài)的圖片(一個x)的加載框來替代菊花或者進(jìn)度條

除此之外你也可以設(shè)置自己的圖片,使用+ (void)showImage:(UIImage*)image status:(NSString*)status;

消失

SVProgressHUD提供了幾種方式來顯示一個Loding框

  • dismiss 直接關(guān)閉一個加載Hub
  • dismissWithDelay 延遲關(guān)閉一個Hub
  • dismissWithCompletion 關(guān)閉Hub帶一個完成的Block
  • dismissWithDelay:completion 延遲關(guān)閉Hub并帶一個完成的Block

可以看出SVProgressHUD提供了簡單明了的API,使用便利的類方法讓我們快速在代碼中使用

深入源碼

顯示過程

SVProgressHUD 比較簡單易懂
在眾多的類方法下面,也實(shí)現(xiàn)相應(yīng)的實(shí)例方法

//簡單版本
- (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)了一個單例,這也不難理解為什么可以在內(nèi)方法中快速的顯示一個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;
}

之后使用這個單例視圖來完成Hub的顯示,那么showProgress這個方法又做什么呢?首先我們看下簡化版代碼

- (void)showProgress:(float)progress status:(NSString*)status {
    __weak SVProgressHUD *weakSelf = self;
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        __strong SVProgressHUD *strongSelf = weakSelf;
        if(strongSelf){
             //更新視圖層級,將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會尋找提供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,計算出大小,然后完成顯示

動畫的繪制

SVIndefiniteAnimatedView是無限旋轉(zhuǎn)的View。其中使用CASharpLayer和LayerMask來完成一個旋轉(zhuǎn)動畫,使用一張漸變的圖片,設(shè)置其mask屬性,然后加上旋轉(zhuǎn)動畫,來形成一個漂亮的加載動畫
動畫繪制代碼

        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)動畫比較簡單,原理是使用CAShareLayer,并且設(shè)置的endPath來完成一個由progress驅(qū)動的進(jìn)度條動畫

 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中添加一個Block的operation(在主線程更改UI)
  • 將自己從SuperView中刪除,
  • 如果指定了延遲,那么設(shè)置一個timer
  • 在消失中如果指定動畫,那么加一個fade的動畫來進(jìn)行消失

小結(jié)

  1. SV將View添加到Window上
  2. SV使用NSOperationQueue 來確保自己在主線程修改UI
  3. SV的動畫使用layer來繪制

優(yōu)點(diǎn):API簡單明了,調(diào)用方便
缺點(diǎn):默認(rèn)在APP的UIWindow,不好定制

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容