SVProgressHUD源码解读(2.0.3)

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

一. 效果预览

  • SVPIndefiniteAnimatedView
无限循环
  • SVProgressAnimatedView
单次滚动
  • SVRadialGradientLayer
渐变视图

二. 类分析

  • SVProgressHUD

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

1. HUD提示框背景

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

2. 遮罩层背景

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

3. 无限循环的显示类型

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

4. 常用属性介绍

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;

5. 常用方法介绍

  • 无限循环状态显示,不会自动小时,需主动调用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;

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

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

** 7.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.overlayView.userInteractionEnabled = YES;
        self.accessibilityLabel = status;
        self.isAccessibilityElement = YES;
    } else {
        self.overlayView.userInteractionEnabled = NO;
        self.hudView.accessibilityLabel = status;
        self.hudView.isAccessibilityElement = YES;
    }
    
    // 设置overlayView为透明色
    self.overlayView.backgroundColor = [UIColor clearColor];
    
    // 根据alpha值判断是是否可见
    if(self.alpha != 1.0f || self.hudView.alpha != 1.0f) {
        // 如果之前不可见则发出SVProgressHUDWillAppearNotification通知,告诉马上显示
        [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDWillAppearNotification
                                                            object:self
                                                          userInfo:[self notificationUserInfo]];
        
        // 缩放效果
        self.hudView.transform = CGAffineTransformScale(self.hudView.transform, 1.3, 1.3);
        
        // 处理初始值处理iOS7及以上不会响应透明度改变的UIToolbar
        self.alpha = 0.0f;
        self.hudView.alpha = 0.0f;
        
        // 定义动画block及完成动画block
        __weak SVProgressHUD *weakSelf = self;
        
        __block void (^animationsBlock)(void) = ^{
            __strong SVProgressHUD *strongSelf = weakSelf;
            if(strongSelf) {
                // Shrink HUD to finish pop up animation
                strongSelf.hudView.transform = CGAffineTransformScale(strongSelf.hudView.transform, 1/1.3f, 1/1.3f);
                strongSelf.alpha = 1.0f;
                strongSelf.hudView.alpha = 1.0f;
            }
        };
        
        __block void (^completionBlock)(void) = ^{
            __strong SVProgressHUD *strongSelf = weakSelf;
            if(strongSelf) {
                // 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
                if(strongSelf.alpha == 1.0f && strongSelf.hudView.alpha == 1.0f){
                    // Register observer <=> we now have to handle orientation changes etc.
                    [strongSelf registerNotifications];
                    
                    // Post notification to inform user
                    [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDDidAppearNotification
                                                                        object:strongSelf
                                                                      userInfo:[strongSelf notificationUserInfo]];
                }
            }
            
            // 更新 accesibilty
            UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil);
            UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, status);
        };
        
        if (self.fadeInAnimationDuration > 0) {
            // 如果设置了动画时间则进行动画效果
            [UIView animateWithDuration:self.fadeInAnimationDuration
                                  delay:0
                                options:(UIViewAnimationOptions) (UIViewAnimationOptionAllowUserInteraction | UIViewAnimationCurveEaseOut | 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)
                    [strongSelf.hudView addSubview:strongSelf.ringView];
                if(!strongSelf.backgroundRingView.superview)
                    [strongSelf.hudView addSubview:strongSelf.backgroundRingView];
                
                // Set progress animated
                [CATransaction begin];
                [CATransaction setDisableActions:YES];
                strongSelf.ringView.strokeEnd = progress;
                [CATransaction commit];
                
                // 更新activityCount
                if(progress == 0) {
                    strongSelf.activityCount++;
                }
            } else {
                // 防止上次为进度的样式导致重叠
                [strongSelf cancelRingLayerAnimation];
                
                // 增加无限旋转视图到hudview上
                [strongSelf.hudView addSubview:strongSelf.indefiniteAnimatedView];
                if([strongSelf.indefiniteAnimatedView respondsToSelector:@selector(startAnimating)]) {
                    [(id)strongSelf.indefiniteAnimatedView startAnimating];
                }
                
                // Update the activity count
                strongSelf.activityCount++;
            }
            
            // 显示提示的文字信息
            [strongSelf showStatus:status];
        }
    }];
}
  • -(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([strongSelf.imageView respondsToSelector:@selector(setTintColor:)]) {
                if (tintedImage.renderingMode != UIImageRenderingModeAlwaysTemplate) {
                    tintedImage = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
                }
                strongSelf.imageView.tintColor = tintColor;
            } else {
                tintedImage = [strongSelf image:image withTintColor:tintColor];
            }
            strongSelf.imageView.image = tintedImage;
            strongSelf.imageView.hidden = NO;
            
            // 更新文字
            strongSelf.statusLabel.text = status;
            
            // 显示文字视图
            [strongSelf showStatus:status];
            
            // 添加定时消失timer
            strongSelf.fadeOutTimer = [NSTimer timerWithTimeInterval:duration target:strongSelf selector:@selector(dismiss) userInfo:nil repeats:NO];
            [[NSRunLoop mainRunLoop] addTimer:strongSelf.fadeOutTimer forMode:NSRunLoopCommonModes];
        }
    }];
}

8. 其它方法

  • 设置hud位置的方法
    - (void)positionHUD:(NSNotification*)notification

  • 调整hud尺寸的方法
    - (void)updateHUDFrame

  • 更新模糊背景视图的方法
    - (void)updateBlurBounds

  • SVPIndefiniteAnimatedView 类

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

mask效果
  • SVProgressAnimatedView 类

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

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

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

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


参考资料
https://github.com/SVProgressHUD/SVProgressHUD
http://www.jianshu.com/p/a08d4597cf24

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容