SVProgressHUD(2.0.3)原来是这样

有段时间没有写了。这个周末抽空简单整理了一下关于自己对SVProgressHUD一些看法以及感悟。过程中自己感受到坚持做开源和坚持写原创文章的不易。时间是每一个程序员最宝贵的资源。

简介

SVProgressHUD在iOS开发中用作提示的场景还是非常多的。这里主要从整个项目的使用及源码方面就行分析以及附上相关效果图。希望能起到抛砖引玉的作用。

使用

SVProgrossHUD是通过单例的方式来使用,这种方式也是许多第三方所使用的。也就是快速创建,不需要手动的alloc进行实例化。

  • 使用的场景: 比较合理的场景是在推荐用户操作之前确定需要执行任务其他任务的时候,而不是在刷新,无限的滑动或者发送消息等场景。

常见的使用方式如下:

[SVProgressHUD show];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 处理耗时的操作
    dispatch_async(dispatch_get_main_queue(), ^{
        [SVProgressHUD dismiss];
    });
});

使用+ (void)show; + (void)showWithStatus:(NSString*)string;来显示状态不明确的操作,用+ (void)showProgress:(CGFloat)progress;+ (void)showProgress:(CGFloat)progress status:(NSString*)status;来显示状态明确的操作,显示当前操作的进度。


取消+ (void)dismiss;+ (void)dismissWithDelay:(NSTimeInterval)delay;这里顺便提一下dismissWithDelay这个方法之前没注意。可以延迟取消,这样就不用手动用GCD的延迟去dismiss了。


如果想平衡调用的次数,可以使用+ (void)popActivity; 一旦匹配了调用show的次数则会消失。如果没有匹配争取则不会消失。其源码为

+ (void)popActivity {
if([self sharedView].activityCount > 0) {
    [self sharedView].activityCount--;
}
if([self sharedView].activityCount == 0) {
    [[self sharedView] dismiss];
}

}

或者根据字符串的长度来自动确定显示的时间。当调用下面的这些方法的时候会用这种方式

  • (void)showInfoWithStatus:(NSString*)string;
  • (void)showSuccessWithStatus:(NSString*)string;
  • (void)showErrorWithStatus:(NSString*)string;
  • (void)showImage:(UIImage)image status:(NSString)string;

我们可以自定义里面的一些属性,比如字体大小,提示图片等。可以自定的方法如下:

+ (void)setDefaultStyle:(SVProgressHUDStyle)style;                  // default is SVProgressHUDStyleLight
  • (void)setDefaultMaskType:(SVProgressHUDMaskType)maskType; // default is SVProgressHUDMaskTypeNone
  • (void)setDefaultAnimationType:(SVProgressHUDAnimationType)type; // default is SVProgressHUDAnimationTypeFlat
  • (void)setMinimumSize:(CGSize)minimumSize; // default is CGSizeZero, can be used to avoid resizing for a larger message
  • (void)setRingThickness:(CGFloat)width; // default is 2 pt
  • (void)setRingRadius:(CGFloat)radius; // default is 18 pt
  • (void)setRingNoTextRadius:(CGFloat)radius; // default is 24 pt
  • (void)setCornerRadius:(CGFloat)cornerRadius; // default is 14 pt
  • (void)setFont:(UIFont*)font; // default is [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline]
  • (void)setForegroundColor:(UIColor*)color; // default is [UIColor blackColor], only used for SVProgressHUDStyleCustom
  • (void)setBackgroundColor:(UIColor*)color; // default is [UIColor whiteColor], only used for SVProgressHUDStyleCustom
  • (void)setBackgroundLayerColor:(UIColor*)color; // default is [UIColor colorWithWhite:0 alpha:0.4], only used for SVProgressHUDMaskTypeCustom
  • (void)setInfoImage:(UIImage*)image; // default is the bundled info image provided by Freepik
  • (void)setSuccessImage:(UIImage*)image; // default is bundled success image from Freepik
  • (void)setErrorImage:(UIImage*)image; // default is bundled error image from Freepik
  • (void)setViewForExtension:(UIView*)view; // default is nil, only used if #define SV_APP_EXTENSIONS is set
  • (void)setMinimumDismissTimeInterval:(NSTimeInterval)interval; // default is 5.0 seconds
  • (void)setFadeInAnimationDuration:(NSTimeInterval)duration; // default is 0.15 seconds
  • (void)setFadeOutAnimationDuration:(NSTimeInterval)duration; // default is 0.15 seconds

SVProgressHUD默认提供两种样式SVProgressHUDStyleLight, SVProgressHUDStyleDark,一个是白色主题,一个是黑色主题如果想自定义一些颜色可以通过setForegroundColor and setBackgroundColor不要忘记设置默认样式 SVProgressHUDStyleCustom


通知,SVProgressHUD会使用到四个通知

SVProgressHUDWillAppearNotification 

SVProgressHUDDidAppearNotification
SVProgressHUDWillDisappearNotification
SVProgressHUDDidDisappearNotification

每一通知会传递一个userinfo字典传递HUD的提示信息,key为``SVProgressHUDStatusUserInfoKey``。当用户触摸提示的整个屏幕的时候会发出``SVProgressHUDDidReceiveTouchEventNotification``通知,当用户直接触摸HUD的时候会发出``SVProgressHUDDidTouchDownInsideNotification``通知。

关键类

SVProgroessHUD一共有四个重要的类。它们分别是

  • SVPIndefiniteAnimatedView:无限旋转视图组件。如下图:
  • SVProgressAnimatedView:进度视图组件.如下图


  • SVProgressHUD: 视图显示控制类(我们通过SVProgressHUD这个类来使用上两种视图组件)。类似于一个管理类。

  • SVRadialGradientLayer:渐变层,当我们设置遮罩样式为SVProgressHUDMaskTypeGradient,就需要用到这个层。模仿系统UIAlterView的背景效果。
  • SVProgressHUD.bundle: 这里面放的是一些图片资源文件

关键类分析

SVPIndefiniteAnimatedView

关于这个类,主要是需要讲的就是一个如果实现无限加载的动画效果。如上图的上图一样。原理其实不难,我这个给出一个图,大家应该就明白了。

  • 原理也就是不断地旋转一张具有渐变颜色的图片,然后通过使用mask来遮住不需要的部分(结合layer使用)。

讲到这里就不得不提到iOS动画中的CALayer以及Mask。常见的场景就是CAShapeLayer和mask结合使用

/* A layer whose alpha channel is used as a mask to select between the
 * layer's background and the result of compositing the layer's
 * contents with its filtered background. Defaults to nil. When used as
 * a mask the layer's `compositingFilter' and `backgroundFilters'
 * properties are ignored. When setting the mask to a new layer, the
 * new layer must have a nil superlayer, otherwise the behavior is
 * undefined. Nested masks (mask layers with their own masks) are
 * unsupported. */

@property(nullable, strong) CALayer *mask;

以上是CALayer的头文件关于mask的说明,mask实际上layer内容的一个遮罩。
如果我们把mask是透明的,实际看到的layer是完全透明的,也就是说只有mask的内容不透明的部分和layer的叠加部分才会显示。如下图:


有许多很炫酷的动画效果都是通过这样实现的。比如以下几种


其中还有Twitter的启动效果

  • 代码片段
        // 初始化,设置参数
        _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;

        // 初始化mask,从资源库中读取图片
        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
        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"];
        // 注意value类型为id类型
        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;
        // 将动画加到mask上
        [_indefiniteAnimatedLayer.mask addAnimation:animation forKey:@"rotate"];

通过旋转动画我们看到的就是


然后来看看动画组

        // 创建动画组,并设置相关属性
        CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
        animationGroup.duration = animationDuration;
        animationGroup.repeatCount = INFINITY;
        animationGroup.removedOnCompletion = NO;
        animationGroup.timingFunction = linearCurve;

        // strokeStart动画
        CABasicAnimation *strokeStartAnimation = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
        strokeStartAnimation.fromValue = @0.015;
        strokeStartAnimation.toValue = @0.515;

        // strokeEnd动画
        CABasicAnimation *strokeEndAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
        strokeEndAnimation.fromValue = @0.485;
        strokeEndAnimation.toValue = @0.985;

        // 将动画加到动画组
        animationGroup.animations = @[strokeStartAnimation, strokeEndAnimation];
        [_indefiniteAnimatedLayer addAnimation:animationGroup forKey:@"progress"];
       

动画组的效果

让我们来找找数字之间的关系

strokeStartAnimation.fromValue = @0.015;
strokeStartAnimation.toValue = @0.515;

strokeEndAnimation.fromValue = @0.485;
strokeEndAnimation.toValue = @0.985;

有些规律吧。这样就能达到不断改变strokeStart和strokeEnd的值并让之间的差值为一个常量。我们看到的就是一个白色的缺口不断旋转的效果。个人觉得实现这种效果是想到巧妙的

改变strokeEnd参数的效果

strokeEndAnimation.fromValue = @0.285;
strokeEndAnimation.toValue = @0.785;
  • 其他

其他值得学习的大概应该算用到了懒加载的方式吧。便于代码的管理以及缕清逻辑关系。重写属性的setter方法,在setter方法里面完成和这个属性相关一些赋值,逻辑判断操作。比如:

- (void)setRadius:(CGFloat)radius {
    if(radius != _radius) {
        _radius = radius;
        
        // 在setter方法中进行相关逻辑判断,
        [_indefiniteAnimatedLayer removeFromSuperlayer];
        _indefiniteAnimatedLayer = nil;
        
        if(self.superview) {
            [self layoutAnimatedLayer];
        }
    }
}

SVProgressAnimatedView

这个是用于处理进度的视图组件,实现进度的原理也很简单,也就是不断改变strokeEnd的值。
来看看那.h文件

@interface SVProgressAnimatedView : UIView

// 半径
@property (nonatomic, assign) CGFloat radius;
// 厚度
@property (nonatomic, assign) CGFloat strokeThickness;
// 进度指示颜色
@property (nonatomic, strong) UIColor *strokeColor;

// 当前进度,
@property (nonatomic, assign) CGFloat strokeEnd;

@end

.m文件的实现大致和SVIndefiniteAnimatedView一样。使用懒加载,在willMoveToSuperview方法中添加layer。实现进度的关键就是重写strokeEnd的setter方法

- (void)setStrokeEnd:(CGFloat)strokeEnd {
    _strokeEnd = strokeEnd;
    // 改变结束的位置
    _ringAnimatedLayer.strokeEnd = _strokeEnd;
}

进度写死的效果0.4

顺便提一下,storkeStart使用的默认值是0。所以是从正上方开始的。

SVProgressHUD

这个类的作用想到于管理类的作用,负责和外部交互和调用视图组件。进行重要逻辑判断。

.h文件

extern相关

来看看.h文件中extern的使用

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

extern NSString * const SVProgressHUDStatusUserInfoKey;

与extern相关的还有const,static等。下面扩展一下

  • const 最好理解,修饰的东西不能被修改.指针类型根据位置的不同可以理解成3种情况:

  • 常量指针
    初始化之后不能赋值,指向的对象可以是任意对象,对象可变。
    NSString * const pt1;

  • 指向常量的指针
    初始化之后可以赋值,即指向别的常量,指针本身的值可以修改,指向的值不能修改
    const NSString * pt2;
  • 指向常量的常量指针
    const NSString * const pt3;
  • extern
    等同于c,全局变量的定义,声明
    extern const NSString * AA;
    定义
    const NSString * AA = @"abc";
  • static
    等同于c,将变量的作用域限定于本文件?
    不同于java C++里面的类变量,oc没有类变量

--

结论

static
// static变量属于本类,不同的类对应的是不同的对象
// static变量同一个类所有对象中共享,只初始化一次const
// static const变量同static的结论I,只是不能修改了,但是还是不同的对象
// extern const变量只有一个对象,标准的常量的定义方法
// extern的意思就是这个变量已经定义了,你只负责用就行了

typedef NS_ENUM

定义常见的枚举,注意命令的方式,***Type值的命名方式 ***TypeLight

UI_APPEARANCE_SELECTOR

UI_APPEARANCE_SELECTOR:这个关键字是外观属性都用到的,用一个例子简单说一下它的作用。
[[UIBarButtonItem appearance] setTintColor:[UIColor redColor]];可以定制应用中所有条形按钮的颜色为redColor。没有这个UI_APPEARANCE_SELECTOR之前,只要一个一个控件的去修改。也就是通过UI_APPEARANCE_SELECTOR可以批量设置控件的颜色了。

深入了解可以看看使用UIAppearance协议自定义视图

当我在做公用组件的时候,一定要记得把默认值是什么要说明一下。

类方法

类方法主要有两大类,一种是set***一种是show**。前者用于设置外观样式,后者是直接使用的方式。
比如:

  • set**
+ (void)setDefaultStyle:(SVProgressHUDStyle)style;                  // default is SVProgressHUDStyleLight
+ (void)setDefaultMaskType:(SVProgressHUDMaskType)maskType;         // default is SVProgressHUDMaskTypeNone
+ (void)setDefaultAnimationType:(SVProgressHUDAnimationType)type;   // default is SVProgressHUDAnimationTypeFlat
  • show**
+ (void)show;
+ (void)showWithMaskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use show and setDefaultMaskType: instead.")));
+ (void)showWithStatus:(NSString*)status;
+ (void)showWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use showWithStatus: and setDefaultMaskType: instead.")));

这里可以注意一下如何标示方法过期的方式maskType __attribute__((deprecated("Use showSuccessWithStatus: and setDefaultMaskType: instead.")))

.m文件

.m文件包含的内容是整个项目中最复杂或者说是最需要梳理与值得学习的。下面重点介绍下.m文件里面的内容。

常量的定义

关于define和statc const定义常量的区别,这里就不讲了。主要是提醒一下,尽量用statci const来定义更符合风格吧。比如
static const CGFloat SVProgressHUDParallaxDepthPoints = 10;

readonly及getter的使用

虽然这样的用法有些麻烦,对于有强迫症的程序员还是蛮推荐这种写法的

@property (nonatomic, readonly, getter = isClear) BOOL clear;

getter方法

- (BOOL)isClear {
    return (self.defaultMaskType == SVProgressHUDMaskTypeClear || 
    self.defaultMaskType == SVProgressHUDMaskTypeNone);
}

事先定义好私有方法,也就是外界不能直接调用的实例方法

这种习惯能够快速的了解整个类一共有哪些方法以及方法归类等。比如:

- (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;

这样比较有条理,私有方法的定义是在类的扩展里面。

使用单例

常见的一些关于UI的第三方都是通过类方法调用,而且全局可以只用一个实例对象来维护就可以了。

+ (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;
}

show方法参数化

关于方法的参数化说白了就是为了实现代码的复用。因为有存在相同的逻辑,则把相同部分抽离出来,不同的部分通过传入不同参数来控制来达到代码的复用。在实际工作中,这一点也非常重要。

经过整理,最终得出所有调用show**方法最终调用的只有两个个方法+ (void)showProgress:(float)progress status:(NSString*)status+ (void)showImage:(UIImage*)image status:(NSString*)status

  • showProgress:(float)progress status:(NSString*)status

当显示的是无限旋转提示的时候,会传入progrerss = -1来区别显示进度的样式。

+ (void)showWithStatus:(NSString*)status {
    [self sharedView];
    [self showProgress:SVProgressHUDUndefinedProgress status:status];
}

这里的SVProgressHUDUndefinedProgress其实是一个常量。其定义为static const CGFloat SVProgressHUDUndefinedProgress = -1;

这里需要提一提的是,设置遮罩样式没有通过参数传递来设置而是通过设置属性的方式来做的。

+ (void)showProgress:(float)progress maskType:(SVProgressHUDMaskType)maskType {
    SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType;
    [self setDefaultMaskType:maskType];
    [self showProgress:progress];
    // 显示完之后回到默认的遮罩样式
    [self setDefaultMaskType:existingMaskType];
}

我简单分析了一下不通过参数来传递遮罩样式的原因应该是为了每次显示完之后保证下一次遮罩的样式依然是默认的样式。可以看到每次调用完show**之后都会把mask恢复到默认值。

  • + (void)showImage:(UIImage*)image status:(NSString*)status

这个方法是会自动消失show**最终会调用的方法,比如

+ (void)showInfoWithStatus:(NSString*)status;
+ (void)showInfoWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use showInfoWithStatus: and setDefaultMaskType: instead.")));
+ (void)showSuccessWithStatus:(NSString*)status;
+ (void)showSuccessWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use showSuccessWithStatus: and setDefaultMaskType: instead.")));
+ (void)showErrorWithStatus:(NSString*)status;
+ (void)showErrorWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use showErrorWithStatus: and setDefaultMaskType: instead.")));

// shows a image + status, use 28x28 white PNGs
+ (void)showImage:(UIImage*)image status:(NSString*)status;
+ (void)showImage:(UIImage*)image status:(NSString*)status maskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use showImage:status: and setDefaultMaskType: instead.")));

UIAccessibility

UIAccessibility协议用于让外界程序了解到自己身的执情情况。Accessibility是一个交互协议,基于查询<->应答,通知<->监听模型的协议。外部程序通过查询来获取APP应答,从而了解程序。另外通过监听来自APP的消息,来通知用户当前状态。

  • 1.常用的协议与元素包括:

UIAccessibility, protocol,核心协议。
UIAccessibilityAction,protocol,添加行为的协议。 UIAccessibilityElement, class。
UIAccessibilityContainer,protocol,容器协议。

  • 2.常用函数 UIAccessibilityPostNotification。

可以看到SVProgressHUD支持UIAccessibility

     // Accessibility support
    self.accessibilityIdentifier = @"SVProgressHUD";
    self.accessibilityLabel = @"SVProgressHUD";
    self.isAccessibilityElement = YES;

看一下官方介绍

/*
 UIAccessibility
 
 UIAccessibility is implemented on all standard UIKit views and controls so
 that assistive applications can present them to users with disabilities.
 
 Custom items in a user interface should override aspects of UIAccessibility
 to supply details where the default value is incomplete.
 
 For example, a UIImageView subclass may need to override accessibilityLabel,
 but it does not need to override accessibilityFrame.
 
 A completely custom subclass of UIView might need to override all of the
 UIAccessibility methods except accessibilityFrame.
 */

showProgress:(float)progress status:(NSString*)status

我们都知道关于UI的操作都需要放在主线程中。一般会通过GCD的方式如下:

dispatch_async(dispatch_get_main_queue(), ^{

    });

但是SVProgressHUD里面用的是NSOperation来实现的,

[[NSOperationQueue mainQueue] addOperationWithBlock:^{

    }];

这也给了我们另外一种方式回到主线程。😄

strong & weak

 __weak SVProgressHUD *weakSelf = self;

    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        __strong SVProgressHUD *strongSelf = weakSelf;
        if(strongSelf){
            // Update / Check view hierachy to ensure the HUD is visible
            [strongSelf updateViewHierachy];
            ......

上面的代码是从源码中摘的,可以看到为了防止循环引用在block外面用了__weak SVProgressHUD *weakSelf = self;而在block里面用了__strong SVProgressHUD *strongSelf = weakSelf;最开始了解这种用法是从AFNetWorking源码中看到了。为了保证在执行block的时候weakSelf还存在(因为可能会延迟调用),所以需要在block里面用__strong在修饰一次weakSelf.

视图显示的逻辑

为了更好地说明问题,我直接在源码中加注释方便说明。

[[NSOperationQueue mainQueue] addOperationWithBlock:^{
        __strong SVProgressHUD *strongSelf = weakSelf;
        // 逻辑严谨,需要判断是否存在
        if(strongSelf){
            // 更新并且检查视图层次确保SVProgressHUD可见
            [strongSelf updateViewHierachy];
            
            // 重置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上,并且设置当前及大怒
                [strongSelf.hudView addSubview:strongSelf.ringView];
                [strongSelf.hudView addSubview:strongSelf.backgroundRingView];
                strongSelf.ringView.strokeEnd = progress;
                
                // 更新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];
                }
                
                strongSelf.activityCount++;
            }
            
            // 显示提示的文字信息
            [strongSelf showStatus:status];
        }
    }];

大致的思路理清了。把显示文字的逻辑和显示进度与旋转的路基分开来实现的。因为显示文字的逻辑是公有的。

上面代码中需要注意的是有一个更新视图层次的方法[strongSelf updateViewHierachy].因为视图层次很有可能在运行的时候被改变,比如通过runtime。所以每次都需要更新一下视图层次,保证能够显示出来。

更新提示文字showStatus:(NSString*)status

还是根据代码来看吧

- (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;
    }
    
    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) {
                /// 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];
    }
}

以后再写吧.....

真的没想到自己把看懂的东西一个个写出来是这么的麻烦,让自己写出来进行分享的时候真的好费时间。这篇文字自己也是抽空一点一点拼凑起来的。

自己也一直想做一个源码分析的系列,前不久看到了github上已经有人有类似的项目。有些触动,有时候自己想法很多而实际动手去做的时间却很少。总的说来自己总结出,分析开源代码不难,难的是如果写好分析文章。

可能分析得有些粗糙。不过也是自己一个字一个字写出来的。坚持下去吧!!

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

推荐阅读更多精彩内容

  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,124评论 29 470
  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,679评论 0 9
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • 从小到大明明很脆弱的内心却总要故装强大,明明也需要别人的帮助却总是告诉自己忍忍就过了,所以无论什么时候只要他人能找...
    小邓同学阅读 434评论 0 0
  • 人呢总有忽如其来的奇思妙想,似乎等了这么久就是在寻找一个可以如此畅所欲言,直抒己意的平台。不需要咬文嚼字,只求一个...
    踏浪的小脚丫阅读 274评论 0 0