• SVProgressHUD源码解读(2.0.3)


    SVProgressHUDiOS开发中比较常用的一个三方库,用来在执行耗时操作或者指示用户操作结果的场合,由于使用简单,功能丰富,交互友好,被广泛应用。本文从源码的角度,解读一下实现的过程,希望能起到抛砖引玉的作用。

    一. 效果预览

    1. SVPIndefiniteAnimatedView

    无限循环

    2. SVProgressAnimatedView

    单次滚动

    3. SVRadialGradientLayer

    渐变视图

    二. 类分析

    1. SVProgressHUD

    这是SVProgressHUD显示提示框的类,提供类方法和属性来进行不同的设置。

    ** HUD提示框背景

    typedef NS_ENUM(NSInteger, SVProgressHUDStyle) {
    	SVProgressHUDStyleLight, // 白色
    	SVProgressHUDStyleDark, // 黑色
    	SVProgressHUDStyleCustom // 用户自定义
    };
    

    ** 遮罩层背景

    typedef NS_ENUM(NSUInteger, SVProgressHUDMaskType) {
    	SVProgressHUDMaskTypeNone = 1, // 默认mask,用户和交互
    	SVProgressHUDMaskTypeClear, // 不允许交互
    	SVProgressHUDMaskTypeBlack, // 不允许交互,遮罩层呈黑色部分透明
    	SVProgressHUDMaskTypeGradient, // 不允许交互,遮罩层呈渐变效果
    	SVProgressHUDMaskTypeCustom // 不允许交互,遮罩层颜色自定义
    };
    

    ** 无限循环的显示类型

    typedef NS_ENUM(NSUInteger, SVProgressHUDAnimationType) {
    	SVProgressHUDAnimationTypeFlat, // SVPIndefiniteAnimatedView
    	SVProgressHUDAnimationTypeNative // 系统的UIActivityIndicatorView
    };
    

    **常用属性介绍

    hud最小尺寸,默认是(100,100)

    @property (assign, nonatomic) CGSize minimumSize UI_APPEARANCE_SELECTOR;

    圆环厚度,默认是2px

    @property (assign, nonatomic) CGFloat ringThickness UI_APPEARANCE_SELECTOR;

    圆环半径,默认是18px

    @property (assign, nonatomic) CGFloat ringRadius UI_APPEARANCE_SELECTOR;

    提示语字体,默认是14px

    @property (strong, nonatomic) UIFont *font UI_APPEARANCE_SELECTOR;

    Image提示框显示时间,默认是5s

    @property (assign, nonatomic) NSTimeInterval minimumDismissTimeInterval;

    ** 常用方法介绍

    • 无限循环状态显示,不会自动小时,需主动调用dismiss方法
    + (void)show;
    + (void)showWithStatus:(NSString*)status;
    
    + (void)dismiss;
    + (void)dismissWithCompletion:(SVProgressHUDDismissCompletion)completion;
    + (void)dismissWithDelay:(NSTimeInterval)delay;
    + (void)dismissWithDelay:(NSTimeInterval)delay completion:(SVProgressHUDDismissCompletion)completion;
    
    • 进度条状态显示
    + (void)showProgress:(float)progress;
    + (void)showProgress:(float)progress status:(NSString*)status;
    
    • 图片状态显示
    + (void)showInfoWithStatus:(NSString*)status;
    + (void)showSuccessWithStatus:(NSString*)status;
    + (void)showErrorWithStatus:(NSString*)status;
    
    • hud距离中心点的偏移量
    + (void)setOffsetFromCenter:(UIOffset)offset;
    + (void)resetOffsetFromCenter;
    

    ** 通知

    通过监听不同的通知事件,可以获取hud的状态

    extern NSString * const SVProgressHUDWillDisappearNotification;
    extern NSString * const SVProgressHUDDidDisappearNotification;
    extern NSString * const SVProgressHUDWillAppearNotification;
    extern NSString * const SVProgressHUDDidAppearNotification;
    

    ** hud显示流程

    SVProgressHUD采用单例模式,简化代码维护;同时,根据SVProgressHUD的层级结构可以看出,从底层到顶层依次是:UIControl (overlayView) -> SVProgressHUD -> UIView (hudView) -> UIVisualEffectView -> AnimatedView (具体动画视图) 。

    • -(void)showStatus:(NSString*)status, 这是显示无限循环状态的提示框,可以添加文字进一步详细补充。其中,SVProgressHUD采用图形和文字分离的模式,方面文字视图的复用。所有,显示文字的视图,最终都会调用下面的方法。
    - (void)showStatus:(NSString*)status {
        // 更新frame及位置,因为frame是更加status来确定的而postion是根据参数控制的。
        [self updateHUDFrame];
        [self positionHUD:nil];
        
        // 更新 accesibilty 和是否可以点击
        if(self.defaultMaskType != SVProgressHUDMaskTypeNone) {
            self.accessibilityLabel = status;
            self.isAccessibilityElement = YES;
        } else {
            self.hudView.accessibilityLabel = status;
            self.hudView.isAccessibilityElement = YES;
        }
        
        // Show if not already visible
        // Checking one alpha value is sufficient as they are all the same
    #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
        if(self.hudView.contentView.alpha != 1.0f){
    #else
    	 根据alpha值判断是是否可见
        if(self.hudView.alpha != 1.0f){
    #endif
            // 如果之前不可见则发出SVProgressHUDWillAppearNotification通知,告诉马上显示
            [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDWillAppearNotification
                                                                object:self
                                                              userInfo:[self notificationUserInfo]];
            
            //缩放效果
            self.hudView.transform = CGAffineTransformScale(self.hudView.transform, 1.3, 1.3);
            
            // Activate blur on view before animation on older iOS versions,
            // as we cannot animate this property and use alpha values instead
    #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
            bool greateriOS9 = kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_9_0;
            if (self.defaultStyle != SVProgressHUDStyleCustom && !greateriOS9) {
                [self addBlur];
                
                // Update alpha
                self.hudView.contentView.alpha = 1.0f;
            }
    #endif
            
            
            // Define blocks
            __block void (^animationsBlock)(void) = ^{
                // Shrink HUD to finish pop up animation
                self.hudView.transform = CGAffineTransformScale(self.hudView.transform, 1/1.3f, 1/1.3f);
                
    #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
                if(self.defaultStyle != SVProgressHUDStyleCustom && greateriOS9){
                    // Fade in blur effect
                    [self addBlur];
                    
                    // Update alpha
                    self.hudView.contentView.alpha = 1.0f;
                } else {
                    // This gives a warning on iOS 8, however it works, see #703
                    self.hudView.alpha = 1.0f;
                }
    #else
                self.hudView.alpha = 1.0f;
                self.hudView.contentView.alpha = 1.0f;
    #endif
                self.backgroundView.alpha = 1.0f;
            };
            
            __block void (^completionBlock)(void) = ^{
                // Check if we really achieved to show the HUD (<=> alpha values are applied)
                // and the change of these values has not been cancelled in between e.g. due to a dismissal
                // Checking one alpha value is sufficient as they are all the same
    #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
                if(self.hudView.contentView.alpha == 1.0f){
    #else
                if(self.hudView.alpha == 1.0f){
    #endif
                    // Register observer <=> we now have to handle orientation changes etc.
                    [self registerNotifications];
                    
                    // Post notification to inform user
                    [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDDidAppearNotification
                                                                        object:self
                                                                      userInfo:[self notificationUserInfo]];
                }
                
                // 更新 accesibilty
                UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil);
                UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, status);
            };
            
            if (self.fadeInAnimationDuration > 0) {
                // 如果设置了动画时间则进行动画效果
                [UIView animateWithDuration:self.fadeInAnimationDuration
                                      delay:0
                                    options:(UIViewAnimationOptions) (UIViewAnimationOptionAllowUserInteraction | UIViewAnimationCurveEaseIn | UIViewAnimationOptionBeginFromCurrentState)
                                 animations:^{
                                     animationsBlock();
                                 } completion:^(BOOL finished) {
                                     completionBlock();
                                 }];
            } else {
                animationsBlock();
                completionBlock();
            }
            
            // 完成了更新视图层次,视图的frame以及视图的各种属性之后,告诉系统稍微进行重绘
            [self setNeedsDisplay];
        }
    }
    
    • -(void)showProgress:(float)progress status:(NSString*)status,这是显示单次滚动效果的提示框,每次显示视图前,都会取消其它视图,防止上次显示不同视图产生的干扰。其中,在设置strokeEnd时,使用事物类CATransaction,确保操作不被干扰。
    - (void)showProgress:(float)progress status:(NSString*)status {
        __weak SVProgressHUD *weakSelf = self;
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            __strong SVProgressHUD *strongSelf = weakSelf;
            if(strongSelf){
                // 更新并且检查视图层次确保SVProgressHUD可见
                [strongSelf updateViewHierarchy];
                
                // 重置imageView和消失时间。防止之前调用过,使用上次存在的样式设置
                strongSelf.imageView.hidden = YES;
                strongSelf.imageView.image = nil;
                
                if(strongSelf.fadeOutTimer) {
                    strongSelf.activityCount = 0;
                }
                strongSelf.fadeOutTimer = nil;
                
                // 更新statusLabel显示的内容和显示的进度
                strongSelf.statusLabel.text = status;
                strongSelf.progress = progress;
                
                //根据progersss的值来确定正确的样式,当progress>=0的时候,显示进度样式,当progress = -1的时候为无限旋转的样式
                if(progress >= 0) {
                    // 防止上次为无限旋转的样式导致重叠
                    [strongSelf cancelIndefiniteAnimatedViewAnimation];
                    
                    // 添加进度视图到hudview上,并且设置当前进度值
                    if(!strongSelf.ringView.superview){
    #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
                        [strongSelf.hudVibrancyView.contentView addSubview:strongSelf.ringView];
    #else
                        [strongSelf.hudView addSubview:strongSelf.ringView];
    #endif
                    }
                    if(!strongSelf.backgroundRingView.superview){
    #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
                        [strongSelf.hudVibrancyView.contentView addSubview:strongSelf.backgroundRingView];
    #else
                        [strongSelf.hudView addSubview:strongSelf.backgroundRingView];
    #endif
                    }
                    
                    // Set progress animated
                    [CATransaction begin];
                    [CATransaction setDisableActions:YES];
                    strongSelf.ringView.strokeEnd = progress;
                    [CATransaction commit];
                    
                    // 更新activityCount
                    if(progress == 0) {
                        strongSelf.activityCount++;
                    }
                } else {
                    // 防止上次为进度的样式导致重叠
                    [strongSelf cancelRingLayerAnimation];
                    
                    // 增加无限旋转视图到hudview上
    #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
                    [strongSelf.hudVibrancyView.contentView addSubview:strongSelf.indefiniteAnimatedView];
    #else
                    [strongSelf.hudView  addSubview:strongSelf.indefiniteAnimatedView];
    #endif
                    if([strongSelf.indefiniteAnimatedView respondsToSelector:@selector(startAnimating)]) {
                        [(id)strongSelf.indefiniteAnimatedView startAnimating];
                    }
                    
                    // Update the activity count
                    strongSelf.activityCount++;
                }
                
                // 显示提示的文字信息
                [strongSelf showStatus:status];
                
                // Tell the Haptics Generator to prepare for feedback, which may come soon
    #if TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000
                [strongSelf.hapticGenerator prepare];
    #endif
            }
        }];
    }
    
    • -(void)showImage:(UIImage*)image status:(NSString*)status duration:(NSTimeInterval)duration,这是显示带Image的提示框,自带info/success/error三种类型,也可以自定义图片。
    - (void)showImage:(UIImage*)image status:(NSString*)status duration:(NSTimeInterval)duration {
        __weak SVProgressHUD *weakSelf = self;
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            __strong SVProgressHUD *strongSelf = weakSelf;
            if(strongSelf){
                // 更新并且检查视图层次确保SVProgressHUD可见
                [strongSelf updateViewHierarchy];
                
                // 重置progress,并取消其它动画
                strongSelf.progress = SVProgressHUDUndefinedProgress;
                [strongSelf cancelRingLayerAnimation];
                [strongSelf cancelIndefiniteAnimatedViewAnimation];
                
                // 更新imageView
                UIColor *tintColor = strongSelf.foregroundColorForStyle;
                UIImage *tintedImage = image;
                if (image.renderingMode != UIImageRenderingModeAlwaysTemplate) {
                    tintedImage = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
                }
                strongSelf.imageView.tintColor = tintColor;
                strongSelf.imageView.image = tintedImage;
                strongSelf.imageView.hidden = NO;
                
                // 更新文字
                strongSelf.statusLabel.text = status;
                
                // 显示文字视图
                [strongSelf showStatus:status];
                
                // An image will be dismissed automatically. Therefore, we start a timer
                // which then will call dismiss after the predefined duration
                // 添加定时消失timer
                strongSelf.fadeOutTimer = [NSTimer timerWithTimeInterval:duration target:strongSelf selector:@selector(dismiss) userInfo:nil repeats:NO];
                [[NSRunLoop mainRunLoop] addTimer:strongSelf.fadeOutTimer forMode:NSRunLoopCommonModes];
            }
        }];
    }
    

    **其它方法

    • 设置hud位置的方法

    - (void)positionHUD:(NSNotification*)notification

    • 调整hud尺寸的方法

    - (void)updateHUDFrame

    • 更新模糊背景视图的方法

    - (void)updateBlurBounds

    2. SVPIndefiniteAnimatedView 类

    这个类提供了一个无线旋转的动画,实现方法是把一个颜色渐变的图片旋转,然后利用UIBezierPath/CAShapeLayer/Mask等遮住不需要的部分,最后利用CABasicAnimation设置无限旋转动画。其中,核心部分是利用layermask属性实现遮罩功能,而mask的实现方法是显示显示bounds的非透明部分,实例图如下:

    mask效果

    3. SVProgressAnimatedView 类

    这个类提供一个画圆环的视图,通过不断改变layerstrokeEnd的值,实现了进度的显示。顺便提一下,storkeStart使用的默认值是0, 所以是从正上方开始的。

    - (void)setStrokeEnd:(CGFloat)strokeEnd {
    	_strokeEnd = strokeEnd;
    	_ringAnimatedLayer.strokeEnd = _strokeEnd;
    }
    

    4. SVRadialGradientLayer 类

    这个类继承自CALayer,通过CGContextDrawRadialGradient来画渐变颜色层;其中,CoreFoundation中通过create创建的需要用release释放,否则会造成内存泄漏。

    至此,SVProgressHUD分析暂告一段落,分析的不全面的地方,欢迎交流。


    参考资料

    https://github.com/SVProgressHUD/SVProgressHUD

    http://www.jianshu.com/p/a08d4597cf24

  • 相关阅读:
    Windows下Goland的Terminal设置为Git Bash
    BoltDB简单使用教程
    Base64编码转换原理
    [区块链|非对称加密] 对数字证书(CA认证)原理的回顾
    [数据库锁机制] 深入理解乐观锁、悲观锁以及CAS乐观锁的实现机制原理分析
    升级mojave后的小问题解决
    ubuntu安装ssh服务记录
    dubbo+maven多模块项目单元测试
    sass与less
    (转)初识 Lucene
  • 原文地址:https://www.cnblogs.com/fishbay/p/7196987.html
Copyright © 2020-2023  润新知