SVProgressHUD框架解析

SVProgressHUD框架的简单易用让他深受多数开发者的青睐,在github上获得了11.5k个小星星!这个框架提供了获取数据时添加加载提示遮罩,获取数据加载完成时显示成功,亦或数据获取失败时的遮罩提示。

演示效果

SVProgressHUD框架解析

代码结构

SVProgressHUD框架解析

 

SVIndefiniteAnimatedView实现

- (CAShapeLayer*)indefiniteAnimatedLayer {
    if(!_indefiniteAnimatedLayer) {
        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*3/2) endAngle:(CGFloat) (M_PI/2+M_PI*5) clockwise:YES];
        
        _indefiniteAnimatedLayer = [CAShapeLayer layer];
        // 省略部分代码
        // 设置绘制的圆形_indefiniteAnimatedLayer的相关属性
        _indefiniteAnimatedLayer.path = smoothedPath.CGPath;
        
        CALayer *maskLayer = [CALayer layer];
        // 省略部分代码
        // 设置遮罩_indefiniteAnimatedLayer的相关属性
        _indefiniteAnimatedLayer.mask = maskLayer;
        
        // 设置圆形layer旋转动画
        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"];
        
        // 设置strokeStart和strokeEnd参数使圆形头和尾产生间距
        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"];
        
    }
    return _indefiniteAnimatedLayer;
}
  • 原理步骤:
  1. 使用图层layer并通过贝塞尔曲线绘制一个圆圈;
  2. 通过获取png图片angle-mask设置为图层layer的mask遮罩,图片可以设置不同透明值来控制图层layer的显示样式;当透明值为0时,图层layer的mask遮罩部分则不会显示,当透明值大于0时,则会按照透明值比例显示图层layer的mask遮罩部分;
  3. 通过CABasicAnimation动画设置图层_indefiniteAnimatedLayer的transform.rotation属性使其旋转;
  4. 通过CAAnimationGroup动画组来设置_indefiniteAnimatedLayer图层strokeStart和strokeEnd属性,使其头尾之间存在一定的距离;
  5. SVIndefiniteAnimatedView用于旋转加载的提示样式;

SVProgressAnimatedView

- (CAShapeLayer*)ringAnimatedLayer {
    if(!_ringAnimatedLayer) {
        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;
    }
    return _ringAnimatedLayer;
}
  1. 通过贝塞尔曲线绘制圆圈_ringAnimatedLayer图层;
  2. SVProgressAnimatedView主要用于加载或上传显示进度的提示样式;

SVRadialGradientLayer

- (void)drawInContext:(CGContextRef)context {
    size_t locationsCount = 2;
    CGFloat locations[2] = {0.0f, 1.0f};
    CGFloat colors[8] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.75f};
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, colors, locations, locationsCount);
    CGColorSpaceRelease(colorSpace);

    float radius = MIN(self.bounds.size.width , self.bounds.size.height);
    CGContextDrawRadialGradient (context, gradient, self.gradientCenter, 0, self.gradientCenter, radius, kCGGradientDrawsAfterEndLocation);
    CGGradientRelease(gradient);
}
  • 通过生成的SVRadialGradientLayer图层调用setNeedsDisplay函数使图层执行drawInContext函数,生成一个渐变颜色的图层;

 

压轴SVProgressHUD

  • 视图层级

SVProgressHUD框架解析

代码结构

  • 仅加载或加载进度样式 原理
+ (void)show {
    [self showWithStatus:nil];
}
+ (void)showWithStatus:(NSString*)status {
    [self showProgress:SVProgressHUDUndefinedProgress status:status];
}
- (void)showProgress:(float)progress status:(NSString*)status {
    __weak SVProgressHUD *weakSelf = self;

    // 任务在主线程队列执行
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        __strong SVProgressHUD *strongSelf = weakSelf;
        if(strongSelf){
            if(strongSelf.fadeOutTimer) {
                strongSelf.activityCount = 0;
            }
            
            // 重置计时器
            strongSelf.fadeOutTimer = nil;
            strongSelf.graceTimer = nil;
            
            // 设置视图层级view
            [strongSelf updateViewHierarchy]; 
            
            // 将图片view进行隐藏
            strongSelf.imageView.hidden = YES;
            strongSelf.imageView.image = nil;
            
            // 设置文字文本及进度
            strongSelf.statusLabel.hidden = status.length == 0;
            strongSelf.statusLabel.text = status;
            strongSelf.progress = progress;
            
            // progress进度大于0
            if(progress >= 0) {
                // Cancel the indefiniteAnimatedView, then show the ringLayer
                [strongSelf cancelIndefiniteAnimatedViewAnimation];
                
                // 添加两个ringView:一个背景的RingView,一个显示进度RingView
                if(!strongSelf.ringView.superview){
                    [strongSelf.hudView.contentView addSubview:strongSelf.ringView];
                }
                if(!strongSelf.backgroundRingView.superview){
                    [strongSelf.hudView.contentView addSubview:strongSelf.backgroundRingView];
                }
                
                // 设置当前进度动画
                [CATransaction begin];
                [CATransaction setDisableActions:YES];
                strongSelf.ringView.strokeEnd = progress;
                [CATransaction commit];
                
                // 记录当前显示进度的activityCount数量
                if(progress == 0) {
                    strongSelf.activityCount++;
                }
            } else {
                // 移除显示进度样式的ringView图层
                [strongSelf cancelRingLayerAnimation]; 
                
                // 添加加载样式的图层
                [strongSelf.hudView.contentView addSubview:strongSelf.indefiniteAnimatedView];
                if([strongSelf.indefiniteAnimatedView respondsToSelector:@selector(startAnimating)]) {
                    [(id)strongSelf.indefiniteAnimatedView startAnimating];
                }
                 
                // 记录当前加载样式图层的个数
                strongSelf.activityCount++;
            }
            
            // graceTimeInterval大于0并且backgroundView透明度为0时执行fadeIn函数
            if (self.graceTimeInterval > 0.0 && self.backgroundView.alpha == 0.0f) {
                strongSelf.graceTimer = [NSTimer timerWithTimeInterval:self.graceTimeInterval target:strongSelf selector:@selector(fadeIn:) userInfo:nil repeats:NO];
                [[NSRunLoop mainRunLoop] addTimer:strongSelf.graceTimer forMode:NSRunLoopCommonModes];
            } else {
            // 执行fadeIn函数
                [strongSelf fadeIn:nil];
            }
        }
    }];
}
- (void)fadeIn:(id)data {

    [self updateHUDFrame];  // 设置各个视图的frame
    [self positionHUD:nil]; // 根据当前屏幕方向或是否有键盘弹出高度计算hudView尺寸并添加动力视觉效果

    // 省略部分代码...
    
    // 获取data是否有时间,有的话则会延迟执行dismiss
    id duration = [data isKindOfClass:[NSTimer class]] ? ((NSTimer *)data).userInfo : data;
    
    // backgroundView透明度不为0时
    if(self.backgroundView.alpha != 1.0f) {
        
        // 发送hudView即将显示的通知
        [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDWillAppearNotification
                                                            object:self
                                                          userInfo:[self notificationUserInfo]];
        
        // 按照1/1.5f比例先缩小
        self.hudView.transform = self.hudView.transform = CGAffineTransformScale(self.hudView.transform, 1/1.5f, 1/1.5f);
        
        __block void (^animationsBlock)(void) = ^{

            // 执行动画时恢复1.0比例
            self.hudView.transform = CGAffineTransformIdentity; 

            // 设置hudView毛玻璃模式及其他显示view的透明度alpha
            [self fadeInEffects]; 
        };
        
        __block void (^completionBlock)(void) = ^{
            // 
            if(self.backgroundView.alpha == 1.0f){
                // 注册通知
                [self registerNotifications];
                
                // 发送hudView显示时的通知
                [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDDidAppearNotification
                                                                    object:self
                                                                  userInfo:[self notificationUserInfo]];
                
                // 根据duration是否不为nil开启定时器定时执行dismiss函数
                if(duration){
                    self.fadeOutTimer = [NSTimer timerWithTimeInterval:[(NSNumber *)duration doubleValue] target:self selector:@selector(dismiss) userInfo:nil repeats:NO];
                    [[NSRunLoop mainRunLoop] addTimer:self.fadeOutTimer forMode:NSRunLoopCommonModes];
                }
            }
        };
        
        // 根据fadeInAnimationDuration设置动画执行函数
        if (self.fadeInAnimationDuration > 0) {
            // Animate appearance
            [UIView animateWithDuration:self.fadeInAnimationDuration
                                  delay:0
                                options:(UIViewAnimationOptions) (UIViewAnimationOptionAllowUserInteraction | UIViewAnimationCurveEaseIn | UIViewAnimationOptionBeginFromCurrentState)
                             animations:^{
                                 animationsBlock();
                             } completion:^(BOOL finished) {
                                 completionBlock();
                             }];
        } else {
            animationsBlock();
            completionBlock();
        }
        [self setNeedsDisplay];
    } else { // fadeInAnimationDuration为nil无需开启定时器,直接执行dismiss函数

        // 根据duration是否不为nil开启定时器定时执行dismiss函数
        if(duration){
            self.fadeOutTimer = [NSTimer timerWithTimeInterval:[(NSNumber *)duration doubleValue] target:self selector:@selector(dismiss) userInfo:nil repeats:NO];
            [[NSRunLoop mainRunLoop] addTimer:self.fadeOutTimer forMode:NSRunLoopCommonModes];
        }
    }
}
  • 当showProgress传入SVProgressHUDUndefinedProgress时:
  1. SVProgressHUDUndefinedProgress时样式为加载提示样式;
  2. 撤销可能加载多个提示导致期间会执行dismiss函数,所以刚开始先将计时器清空;
  3. 根据当前HUD样式样式调整一下当前HUD的视图层级结构;
  4. 隐藏图片view和通过判断加载字符串是否显示文字label;
  5. 取消移除加载进度的layer,为了防止上一个可能是加载进度的样式影响到了当前的只加载样式;
  6. 将图层indefiniteAnimatedView样式添加到hudView的contentView上;
  7. 若graceTimeInterval大于0且backgroundView透明度为0时,按照时间添加graceTimer计时器定时执行fadeIn函数;反之直接执行fadeIn函数;
  8. 设置加载hudView尺寸及位置后通过动画进行先缩小再放大到正常比例显示;
  • 当showProgress传入大于0时:
  1. progress大于0时显示加载进度提示样式;
  2. 撤销可能加载多个提示导致期间会执行dismiss函数,所以刚开始先将计时器清空;
  3. 根据当前HUD样式样式调整一下当前HUD的视图层级结构;
  4. 隐藏图片view和通过判断加载字符串是否显示文字label;
  5. 取消移除只加载样式的layer,为了防止上一个可能是只加载的样式影响到了当前的进度加载样式;
  6. 将加载进度背景ringView和提示加载进度的ringView添加到hudView上;
  7. 设置加载hudView尺寸及位置后通过动画进行先缩小再放大到正常比例显示;
  8. 通过progress的值来设置ringView的进度大小;
  • showImage图片提示样式
- (void)showImage:(UIImage*)image status:(NSString*)status duration:(NSTimeInterval)duration {
    __weak SVProgressHUD *weakSelf = self;
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        __strong SVProgressHUD *strongSelf = weakSelf;
        if(strongSelf){

            strongSelf.progress = SVProgressHUDUndefinedProgress;
            [strongSelf cancelRingLayerAnimation];
            [strongSelf cancelIndefiniteAnimatedViewAnimation];
            
            // Update imageView
            if (self.shouldTintImages) {
                if (image.renderingMode != UIImageRenderingModeAlwaysTemplate) {
                    strongSelf.imageView.image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
                }
                strongSelf.imageView.tintColor = strongSelf.foregroundColorForStyle;
            } else {
                strongSelf.imageView.image = image;
            }
            strongSelf.imageView.hidden = NO;

            strongSelf.statusLabel.hidden = status.length == 0;
            strongSelf.statusLabel.text = status;

            if (self.graceTimeInterval > 0.0 && self.backgroundView.alpha == 0.0f) {
                strongSelf.graceTimer = [NSTimer timerWithTimeInterval:self.graceTimeInterval target:strongSelf selector:@selector(fadeIn:) userInfo:@(duration) repeats:NO];
                [[NSRunLoop mainRunLoop] addTimer:strongSelf.graceTimer forMode:NSRunLoopCommonModes];
            } else {
                [strongSelf fadeIn:@(duration)];
            }
        }
    }];
}
  1. 重置计时器,设置view视图层级结构;
  2. 将加载进度或加载的样式移除,将中间显示为imageView并设置图片image;
  3. 其它设置跟上面类似;
  • 执行dismiss移除遮罩
- (void)dismissWithDelay:(NSTimeInterval)delay completion:(SVProgressHUDDismissCompletion)completion {
    __weak SVProgressHUD *weakSelf = self;
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        __strong SVProgressHUD *strongSelf = weakSelf;
        if(strongSelf){
            
            __block void (^animationsBlock)(void) = ^{
                // 按比例缩放1/1.3f比例
                strongSelf.hudView.transform = CGAffineTransformScale(strongSelf.hudView.transform, 1/1.3f, 1/1.3f);

                // 将view设置为透明
                [strongSelf fadeOutEffects];
            };
            
            __block void (^completionBlock)(void) = ^{
                // 将self中的所有view移除了,并重置初始值
                if(self.backgroundView.alpha == 0.0f){

                    [strongSelf.controlView removeFromSuperview];
                    [strongSelf.backgroundView removeFromSuperview];
                    [strongSelf.hudView removeFromSuperview];
                    [strongSelf removeFromSuperview];
 
                    strongSelf.progress = SVProgressHUDUndefinedProgress;
                    [strongSelf cancelRingLayerAnimation];
                    [strongSelf cancelIndefiniteAnimatedViewAnimation];
                    
                    [[NSNotificationCenter defaultCenter] removeObserver:strongSelf];
                    
                    [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDDidDisappearNotification
                                                                        object:strongSelf
                                                                      userInfo:[strongSelf notificationUserInfo]];

#if !defined(SV_APP_EXTENSIONS) && TARGET_OS_IOS
                    UIViewController *rootController = [[UIApplication sharedApplication] keyWindow].rootViewController;
                    [rootController setNeedsStatusBarAppearanceUpdate];
#endif

                    if (completion) {
                        completion();
                    }
                }
            };
            
            // 根据delay判断是否需要延时执行动画
            dispatch_time_t dipatchTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC));
            dispatch_after(dipatchTime, dispatch_get_main_queue(), ^{
                if (strongSelf.fadeOutAnimationDuration > 0) {
                    [UIView animateWithDuration:strongSelf.fadeOutAnimationDuration
                                          delay:0
                                        options:(UIViewAnimationOptions) (UIViewAnimationOptionAllowUserInteraction | UIViewAnimationCurveEaseOut | UIViewAnimationOptionBeginFromCurrentState)
                                     animations:^{
                                         animationsBlock();
                                     } completion:^(BOOL finished) {
                                         completionBlock();
                                     }];
                } else {
                    animationsBlock();
                    completionBlock();
                }
            });
            
            // Inform iOS to redraw the view hierarchy
            [strongSelf setNeedsDisplay];
        }
    }];
}
  • 通过动画将SVProgressHUD中的所有view设置为透明,动画完成时将所有view移除!

 

其它

  • 其它样式
  1. showInfoWithStatus
  2. showSuccessWithStatus
  3. showErrorWithStatus

原理:通过设置不同的图片来显示上面几种类型的提示,显示弹框时间默认是按照显示提示语长度来计算,也可以用户自己设置需要显示的时间长;

  • popActivity

作用:在之前有记录过当前提示加载的个数,这里是退出当前加载的个数;