美文网首页项目经验常看学无止境
SVProgressHUD(2.0.3)原来是这样

SVProgressHUD(2.0.3)原来是这样

作者: 纸简书生 | 来源:发表于2016-05-15 22:02 被阅读27592次

有段时间没有写了。这个周末抽空简单整理了一下关于自己对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上已经有人有类似的项目。有些触动,有时候自己想法很多而实际动手去做的时间却很少。总的说来自己总结出,分析开源代码不难,难的是如果写好分析文章。

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

相关文章

网友评论

  • Yinper:1> 有没有 设置HUD最大显示时间API, 例如超过网络15秒无响应自动隐藏 HUD ?
    2> SVProgressHUDDidReceiveTouchEventNotification 触摸时间监听隐藏遮罩, 有没有提供 API 全局配置触摸隐藏, 用通知太繁琐了?
    感谢楼主告知:sob:
  • RocketsChen:很棒,值得学习:+1:
  • feng_dev:前景色白色,图片也白色了,怎么破?
  • dequal: [SVProgressHUD setErrorImage:errorImage]; 这样可以设置自定义图片 那么问题来了, 图片的大小可以设置吗?
    NotFunGuy:可以设置的,虽然SVP中imageView的大小固定为28x28,imageView是.m文件里的私有属性,但是可以通过Extension创建sharedView方法的前向引用,然后通过KVC获取私有属性修改size
  • Weartist:写的很好,学习了,多谢
  • okios:好像只能显示在屏幕中央,想让弹框在底部显示找了半天都没找到
  • 583a28b22745:我用[SVProgressHUD showImage:img status:@"加载中"]; 显示请求数据时的加载动画,但是这个方法会自动隐藏,怎么能让其像[SVProgressHUD show];一样一直加载,等到接收到数据的时候收到隐藏????请教
  • Fluent:我想实现只有导航栏可以操作,有没有好的办法
  • Link913:楼主你好我如果不想把他加载到正中央该怎么做呢
  • 大城子:请教一下, 楼主是怎么解决, 多个地方调用svp的呢?比如这里调用dismiss 另一个地方调用show, 会导致, 调用show的地方show的时间很短
  • liwb:作者你好,我在对 SVProgressHUD 进行了一系列的自定义
    [SVProgressHUD setDefaultStyle:SVProgressHUDStyleCustom];
    [SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeClear];
    [SVProgressHUD setBackgroundColor:[UIColor colorWithRed:0 green:0 blue:0 alpha:0.6]];
    [SVProgressHUD setBackgroundLayerColor:[UIColor colorWithRed:0 green:0 blue:0 alpha:0.6]];
    [SVProgressHUD setForegroundColor:[UIColor whiteColor]];
    [SVProgressHUD setDefaultAnimationType:SVProgressHUDAnimationTypeFlat];
    自定义后再次调用 SVProgressHUD showWithStatus 的方法后会出现 转的圈圈加粗的情况,请问作者遇到过这种情况吗?
  • 57951c126c15:大神,怎么设置它在屏幕上的显示位置?
  • 00bcb539f3e2:请教一下,是不是+ (void)showImage:(UIImage*)image status:(NSString*)status 方法加载的自定义图片大小只能是28x28 而且都会被渲染成黑色?有没有什么办法?
    00bcb539f3e2:@纸简书生 关于渲染模式那部分的我已经把框架源码改了,但是图片尺寸的入口没找到。
    纸简书生:@这个少年有故事 图片的渲染模式在SVProgerssHUD写死了的。可以看看关于渲染模式http://stackoverflow.com/questions/21200582/uiimage-renderingmode-uiimagerenderingmodeautomatic-vs-uiimagerenderingmodealw。不改动第三方源码的情况下可以用Swizzling,把那部分代码替换掉。
  • 桔呢:如何设置SVProgressHUD位置
  • YHWXQ简简单单的生活:请教一下,我使用 SVProgressHUD.showSuccessWithStatus("用户已绑定过邮箱") 但是运行不显示?谢谢
  • leftwater:怎么自定义显示大小了
  • 朋友有朋:对你表示赞赏,鼓励你继续
  • 超_iOS:这个第三方是不是不需要二次封装啊?
  • 李大宽:楼主, SVPorgressHUD的 三个方法+show / +showWithStatus: / +showProgress...这三个方法设置对于我设置的最小消失事件不起作用, 就是一旦显示出来就不会自动消失... 使用GCD延时操作主动让他们消失有时候会有很多BUG, 比如压根就show不出来了.....dismissWithDelay这个方法也是有问题的, 直接使用会导致show不出来, 不知道楼主对这三个类方法有什么好的解决办法吗?
  • 在没老之前:楼主,[SVProgressHUD show]这个方法是直接显示一个蒙版盖住整个屏幕,但是我想要的效果蒙版盖住除了导航条的所有区域,让用户在网络不好的情况下,也可以返回。应该怎么做啊?
    马铃薯蜀黍:是啊!想知道
    f64a05c89fc7:@在没老之前 同问啊
    Hyukooooh:@在没老之前 同问呀
  • plantseeds:请问如果我只想显示文字,不想显示图片状态,该怎么处理呢?
    plantseeds:@lg_pursuing [SVProgressHUD setInfoImage:nil];然后再[SVProgressHUD showInfoWithStatus:@"Useful Information."];可以,但是感觉不太好
  • _君莫笑_:感谢楼主 :+1: 但是有一个问题我的刷新为啥位置偏?有什么好的解决办法不?
  • 一只啃楠木的鱼:博主,我在XCODE7里开发一个项目,加入SVP后发现 这提示动画很靠顶部 点击一下后又正常了 请问下这是怎么回事 是不是ios9导航栏的原因
    一只啃楠木的鱼:@一只啃楠木的鱼 补充一下 其他地方正常 主要在我登录注册那里 只要我点击textfield 再点击button SVP就很高 如果不点击textfield 直接点击button 高度就正常了
  • 5f13ca18dada:博主,我发现用2.0.3版本的使用showSuccessWithStatus,showErrorWithStatus显示的时间比以前久了,有办法缩段时间吗,
    457787b486dc:@过去如风 minimumDismissTimeInterval // default is 5.0 seconds
    详情见源码
  • 消逝彼得:你好,我给hud自定义了一组动画,但是大小却一直不能调,楼主有啥办法没?
    纸简书生:@消逝彼得 是在动画的时候改变大小么。最好看一下你的代码
  • 戴仓薯:想知道楼主在github上看到的源码分析的项目:)谢谢~
    戴仓薯:@纸简书生 谢谢~~
    纸简书生:@戴仓薯 https://github.com/Draveness/iOS-Source-Code-Analyze
  • AllenYukin:写的真好 cool

本文标题:SVProgressHUD(2.0.3)原来是这样

本文链接:https://www.haomeiwen.com/subject/qiavrttx.html